Javascript是如何工作的: Engine, Runtime 和 Call Stack的概述

spursyy發表於2018-03-13

文章翻譯自How Javascript work系列的第一篇How JavaScript works: an overview of the engine, the runtime, and the call stack

Javascript語言越來越流行,無論個人還是團隊可以把它應用在前端、後端、混合app、嵌入式等越來越多的技術方向上。

本篇將是我關於Javascript是如何工作的系列中的第一篇文章,深度理解Javascript中building blocks*(保證文章的嚴謹,Javascript中的專有名詞我會保留翻譯)*和如何組織building blocks工作的原理將會有利於程式設計師寫出更加健壯的程式碼。我也會在這個文章系列中分享一些在我們構建SessionStack時,為保證輕量級的Javascript應用更健壯、更高的效能表現所使用到的一些不被大眾熟知的特性。

通過GitHut Stats的排名,我們可以看出Javascript在Github倉庫活躍度和提交程式碼總量兩個維度上都是排在第一名,在其它衡量標準的維度上Javascript的表現也是可圈可點。

GitHut.png

如果專案對Javascript的依賴強度很大,為了構建一個穩定的程式每位開發者都必須瞭解Javascript的特性和Javascript的內部原理。

但現實狀況卻非如此,儘管大批的開發者中每天都在使用Javascript作為日常基礎開發語言,對Javascript是如何工作的原理卻知之甚少。

概述

幾乎每位開發者都聽說過V8引擎的概念,許多開發者知道Javascript是單執行緒或Javascript使用callback佇列。

在這篇文章中,我將詳細闡述這些概念以及Javascript是如何工作的。這些細節將有助你寫出更健壯、非阻塞的應用程式。

如果你是一位Javascript新手,這一系列文章將會讓你知道為什麼Javascript相較其它開發語言是如此驚豔。

如果你是一位資深Javascript開發者,我多希望這一系列的文章可以讓你對每天使用的Javascript Runtime有一些新的洞見。

Javascript引擎

Google’s V8是流行的Javascript引擎之一,它使用在Chrome瀏覽器和Node.js中。下面是V8引擎一個簡化的檢視:

V8引擎.png

V8引擎主要包含兩個部分:

  • Memory Heap — 分配記憶體將會在這裡發生
  • Call Stack — 回撥函式將會在這裡執行

Runtime

有一些APIs被開發者在瀏覽器中經常使用到(如:“setTimeout”),然而這些APIs也許並不是由Javascript引擎提供的。

它們來自於哪裡呢? 它們的來歷有些複雜。

Runtime.png

諸如DOM、AJAX、setTimeout等其它是由瀏覽器提供的,我們稱之為WEB APIs。

接下來,我們將談談非常流行的callback queueevent loop

Call Stack

Javascript是一種單執行緒的程式語言,這導致它只有單一的Call Stack。因此在某一時刻,他只能做一件事。

Call Stack是一種資料結構,他主要是記錄Javascript整個執行過程。當Javascript的虛擬機器執行一個函式,就會把這個函式推送到Call Stack中。當這個函式返回值或是執行完畢後,這個函式就會從Call Stack刪除。

讓我們一起看一個例子,注意下面的程式碼:

function multiply(x, y) {
    return x * y;
}
function printSquare(x) {
    var s = multiply(x, x);
    console.log(s);
}
printSquare(5);
複製程式碼

當Javascript引擎在執行這段程式碼的前一刻,Call Stack是空的。然後Call Stack將會按照下圖發生變化。

CallStack.png

看下面的程式碼,這段程式碼模擬在Call Stack中出現異常後的全過程。

function foo() {
    throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
    foo();
}
function start() {
    bar();
}
start();
複製程式碼

假設這段程式碼在foo.js中,foo.js在chrome瀏覽器執行後將會出現下面的堆疊追蹤記錄。

StackTrace.png

**堆疊溢位:**Javascript引擎產生的堆疊超過Javascript執行環境所提供的最大數量。這種異常在程式碼中存在遞迴但沒有設定遞迴結束的條件時,尤其容易產生。 下面就是這種型別的程式碼:

function foo() {
    foo();
}
foo();
複製程式碼

Javascript引擎執行這段程式碼是從foo函式開始,在這個函式中不斷呼叫自己並沒有設定終止條件,從而產生無限迴圈。每一次執行foo,Call Stack都會新增一次函式。這就像下面顯示的那樣:

Overflowing.png

當Javascript引擎中的Call Stack的長度,超過Javascript執行環境中Call Stack的實際長度時,Javascript執行環境(Chrome瀏覽器或Node)就會丟擲下面的異常。

Exception.png

在多執行緒環境中,要考慮諸如死鎖等複雜執行過程。單執行緒的環境中相比較要簡單很多,但是單執行緒同樣有它的限制。Javascript單執行緒的執行環境中,如何應對複雜的呼叫,單執行緒會不會限制程式的效能。

併發(concurrence)與時間迴圈(Event Loop)

當在你的Call Stack中存在一個需要佔用相當大執行時間的函式時,將會發生什麼。例如在瀏覽器中通過Javascript傳輸一個比較大的image檔案時,你會怎麼做?

你也許會問這怎麼也算是一個問題。當Call Stack有待執行的函式時,瀏覽器會阻塞在這裡,並不做其它的任務。這也意味著你不可能在app中呈現流暢複雜的UI。

問題不僅僅如此,一旦Call Stack中等待執行的任務很多時,瀏覽器要在很長的時間內都不能迴應其它事件。許多瀏覽器這時都會丟擲一個提示資訊,徵求你是否要關閉頁面。

PopError.jpeg
這樣必然將導致非常差的使用者體驗。

我們如何在複雜的環境下既不阻塞UI同時也不使瀏覽器長時間沒有迴應?好吧,這裡我就不賣關子了,可以使用非同步回撥。 這將會是我在Javascript是如何工作的系列文章的第二篇《V8引擎的核心和5個寫出高質量程式碼的技巧》

相關文章