JavaScript是如何工作的:引擎的概述、運(yùn)行時(shí)、調(diào)用棧

譯者:Ar0nW

譯文地址:https://ar0n.wang/2017/08/31/how-does-javascript-actually-work-part-1/

原文地址:https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf

由于JavaScript越來越受歡迎,許多開發(fā)團(tuán)隊(duì)也將其利用在許多層面上,前端,后端,混合型應(yīng)用,嵌入式設(shè)備等等。

Githutstats, JavaScript在github的活躍倉(cāng)庫(kù)和Push統(tǒng)計(jì)上名列前茅。在其他排行上,也沒有落后于其他倉(cāng)庫(kù)的活躍程度。

查看最新的GitHub語言統(tǒng)計(jì)

如果項(xiàng)目較多地依賴于JavaScript,那么意味著開發(fā)人員必須更深入的了解內(nèi)部機(jī)制,然后利用JavaScript語言的一切和生態(tài)系統(tǒng)去構(gòu)建一個(gè)令人驚奇的應(yīng)用。

事實(shí)證明,有很多的開發(fā)人員每天都在使用JavaScript,但并不知道底層做了哪些操作。

概述


大多數(shù)人已經(jīng)知道V8引擎是一個(gè)什么樣的概念,并且大多數(shù)人都知道JavaScript單線程的,或者說它使用了一個(gè)回調(diào)隊(duì)列。

在這篇文章中,我們會(huì)詳細(xì)講解一下這些概念并解釋JavaScript是如何運(yùn)行的。通過了解這些細(xì)節(jié),你將可以正確地利用提供的API寫出更好的、非阻塞的應(yīng)用。

如果你是JavaScript新手,這篇文章將會(huì)讓你明白為什么JavaScript與其他語言對(duì)比起來有如此多的“怪異”特性。

如果你是個(gè)有經(jīng)驗(yàn)的JavaScript開發(fā)人員,希望它會(huì)帶給你一些關(guān)于JavaScript運(yùn)行時(shí)的新看法。

JavaScript引擎


一個(gè)流行的JavaScript引擎的例子是google的V8引擎, 被應(yīng)用在Chrome和Node.js的內(nèi)部。這里有一個(gè)簡(jiǎn)化的例子:

這個(gè)引擎由兩個(gè)主要的組件構(gòu)成:

內(nèi)存堆 — 這是發(fā)生內(nèi)存分配的地方

調(diào)用棧 — 這是你的代碼在棧幀中執(zhí)行的地方

運(yùn)行時(shí)


有些API在瀏覽器中幾乎被所有的開發(fā)人員使用過(例如:setTimeout)。然而這些API并不是引擎提供的。

所以,它們是從哪兒來的呢?

事實(shí)證明,現(xiàn)實(shí)是有點(diǎn)兒復(fù)雜的。

因此,實(shí)際上除了JavaScript引擎以外,還有其他的組件。其中有個(gè)組件就是由瀏覽器提供的,叫Web APIs,像DOM,AJAX,setTimeout等等。

然后還有就是非常受歡迎的事件循環(huán)回調(diào)隊(duì)列

調(diào)用棧


JavaScript是單線程的編程語言,意味著它有一個(gè)單一的調(diào)用棧。因此它只能在同一時(shí)間做一件事情。

調(diào)用棧是一種數(shù)據(jù)結(jié)構(gòu),它基本上記錄了我們?cè)诔绦蛑械氖裁次恢谩H绻覀儾饺胍粋€(gè)函數(shù)中,我們會(huì)把這些數(shù)據(jù)放在堆棧的頂部。如果我們從一個(gè)函數(shù)中返回,這些數(shù)據(jù)將會(huì)從棧頂彈出。這就是堆棧的用途。

我們來看個(gè)例子:

function multiply(x, y) {

? ? ?return x * y;

}

function printSquare(x) {

? ? ?vars = multiply(x, x);

? ? ?console.log(s);

}

printSquare(5);

當(dāng)JavaScript引擎開始執(zhí)行這段代碼的時(shí)候,調(diào)用棧是空的。

之后將會(huì)執(zhí)行如下的步驟:

調(diào)用棧中的每個(gè)條目叫做棧幀

這就是當(dāng)一個(gè)異常拋出時(shí),堆棧跟蹤是如何被構(gòu)造的 — 當(dāng)異常發(fā)生時(shí),這基本上是 調(diào)用棧的狀態(tài)。讓我們來看如下的代碼:

function foo() {

? ? ?thrownewError('SessionStack will help you resolve crashes :)');

}

function bar() {

? ? ?foo();

}

function start() {

? ? ?bar();

}

start();

如果這段代碼是在Chrome中執(zhí)行(假設(shè)這段代碼是在foo.js這個(gè)文件里面),以下的堆棧跟蹤將會(huì)產(chǎn)生:

“Blowing the stack”– 這一切發(fā)生在達(dá)到調(diào)用棧最大值的時(shí)候。這種情況可能很容易發(fā)生,尤其是當(dāng)你使用遞歸并且沒有全面地測(cè)試這段代碼的時(shí)候。讓我們來看下示例代碼:

function foo() {

? ? ?foo();

}

foo();

當(dāng)引擎開始執(zhí)行這段代碼的時(shí)候,它首先調(diào)用foo()函數(shù),然而,這個(gè)函數(shù)是遞歸的,并且調(diào)用自身而且沒有任何終止條件。因此在每一次執(zhí)行時(shí),同樣的函數(shù)將一次又一次地添加到調(diào)用棧上。這看起來像是這樣的:

在某個(gè)時(shí)刻,函數(shù)調(diào)用的數(shù)量會(huì)超過調(diào)用棧的大小,瀏覽器將會(huì)決定采取行動(dòng),通過拋出一個(gè)錯(cuò)誤,看起來像這樣:

在單線程中運(yùn)行代碼可以變得很輕松,因?yàn)槟悴槐靥幚碓诙嗑€程環(huán)境中產(chǎn)生的復(fù)雜場(chǎng)景 — 例如,死鎖

但是在單線程上運(yùn)行是很受限制的。因?yàn)镴avaScript只有一個(gè)單一的調(diào)用棧,當(dāng)它的運(yùn)行變慢時(shí)發(fā)生了什么?

并發(fā)和事件循環(huán)


當(dāng)你在調(diào)用棧上為了處理一個(gè)函數(shù)調(diào)用時(shí)花費(fèi)了大量的時(shí)間會(huì)發(fā)生什么?舉個(gè)例子,想象一下,你想要在瀏覽器中使用JavaScript去做一些復(fù)雜的圖像變換。

你可能會(huì)問 — 這為什么是一個(gè)問題?問題是當(dāng)調(diào)用棧上有函數(shù)在執(zhí)行時(shí),瀏覽器實(shí)際上還不能做其他事 — 因?yàn)樗蛔枞恕_@意味著瀏覽器不能做渲染,它不能運(yùn)行任何其他的代碼,它只是卡住了。如果你想要在你的應(yīng)用中有一個(gè)流暢的界面,這就產(chǎn)生了問題。

而且這不是僅有的問題。一旦你的瀏覽器開始在調(diào)用棧上處理非常多的事務(wù)時(shí),它可能會(huì)停止響應(yīng)很長(zhǎng)一段時(shí)間。和大多數(shù)瀏覽器一樣會(huì)引發(fā)一個(gè)錯(cuò)誤,問你是否要終止這個(gè)web頁面。

現(xiàn)在,這不是最好的用戶體驗(yàn),對(duì)嗎?

因此, 我們?cè)鯓硬拍茉趫?zhí)行大量代碼的時(shí)候不會(huì)阻塞用戶界面并造成瀏覽器的未響應(yīng)?解決辦法是異步回調(diào)

這將在《JavaScript是如何工作的》第2部分中詳細(xì)解釋,請(qǐng)繼續(xù)關(guān)注:)

在此期間,如果你正在為你的JavaScript Web應(yīng)用重現(xiàn)和理解某些問題處于困難期,那就去SessionStack看一看。SessionStack會(huì)記錄你的Web應(yīng)用上的任何東西:所有DOM的變化,用戶交互,JavaScript異常,堆棧追蹤,失敗的網(wǎng)絡(luò)請(qǐng)求,和調(diào)試信息。

使用SessionStack,你可以重放在web應(yīng)用中的問題,并且你將看到發(fā)生在你的用戶那兒的所有問題。

有一個(gè)免費(fèi)使用的計(jì)劃允許你使用它

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容