[譯文] JavaScript工做原理:引擎、運行時、調用棧概述

原文 How JavaScript works: an overview of the engine, the runtime, and the call stackjavascript

隨着 JavaScript 愈來愈流行,開發團隊也更多地利用其來支持技術棧的各方面,前端、後端、混合應用、嵌入式設備等。前端

本文是旨在深刻挖掘 JavaScript 其工做原理系列教程的首篇:咱們認爲經過了解 JavaScript 的構建單元並熟悉它們是怎樣結合起來的,有助於你寫出更好的代碼和應用。咱們也會分享一些在構建 SessionStack 應用時用到的經驗法則,爲了維持其競爭力它是一個健壯、高性能的輕量級 JavaScript 應用。java

GitHut stats所示,JavaScript 在活躍倉庫數和GitHub總推送數方面位於首位。在其餘類別排名中落後的也很少。git

GitHut stats

(查看最新的統計)github

若是項目變得如此依賴 JavaScript ,這就意味着開發者必須更加深刻地理解其內部原理以充分利用語言和其生態提供的全部內容,從而構建更棒的軟件。編程

事實顯示,許多開發者天天都在使用 JavaScript 殊不知其底層發生了什麼。後端

概述

幾乎每一個人都據說過 V8 引擎的概念,大多數人也知道 JavaScript 是單線程的或者使用回調隊列。瀏覽器

在本文中,咱們會詳細講解這些概念並闡述 JavaScript 是如何運行的。經過了解這些細節,你就能夠利用提供的 APIs 寫出更好的、無阻塞的應用。session

若是你對 JavaScript 相對陌生,這個博客能夠幫助你理解爲什麼與其餘語言相比 JavaScript 如此怪異。數據結構

若是你是位經驗豐富的 JavaScript 開發人員,也但願能提供給你一些天天都在使用的 JavaScript 運行時實際運做機制的新看法。

JavaScript引擎

JS引擎的一個最流行的例子就是谷歌的 V8V8 引擎使用在例如 Chrome 瀏覽器和 Node.js 中。下圖是一個引擎組成部分的極簡視圖:

JS Engine

引擎由如下兩個主要部分組成:

  • 內存堆——這是內存分配發生的地方
  • 調用棧——這是代碼執行時的堆棧幀所在位置

運行時

幾乎全部 JavaScript 開發者都使用過瀏覽器提供的 APIs(如 setTimeout)。可是那些 APIs 並不禁引擎提供。

那麼,它們來自哪裏?

其實實際狀況更加複雜一些。

JS Runtime

因此,除了引擎以外實際上還有更多東西。咱們還有那些由瀏覽器提供 Web APIs,如 DOMAJAXsetTimeout 等等。

而且,咱們還有很是流行的事件循環回調隊列

調用棧

JavaScript 是單線程編程語言,意味着它只有單一的調用棧。所以它一次只能作一件事。

調用棧是一種數據結構,基本記錄了程序運行的位置。若是進入一個函數,就會把它推入到棧頂部。若是函數返回,就會將函數從棧頂部移除。這就是棧能作的事情。

舉個例子,先來看以下所示的代碼:

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

當引擎開始執行這段代碼時,調用棧將是空的。以後的步驟以下圖所示:

Call Stack

調用棧的每一次進入稱爲棧幀。

這正是拋出異常時棧追蹤的構造過程——這基本上就是異常拋出時調用棧的狀態。看看下面的代碼:

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

Chrome 中執行這段代碼時(假設這些代碼在foo.js文件中),會產生以下的棧追蹤記錄:

Stack Frame

棧溢出」——發生在達到最大調用棧的大小時。這很是容易發生,尤爲是當你使用了遞歸而未進行足夠的測試時,看看以下示例代碼:

function foo() {
    foo();
}
foo();
複製代碼

當引擎開始執行這段代碼時,從調用 foo 函數開始。然而這個函數是遞歸的,它開始調用本身而沒有任何終止條件。因此在執行的每一步中,相同的函數一次又一次添加到調用棧裏。它看起來是這樣的:

Blowing the stack

可是,在某個時候,調用棧中函數的數量超過了它的實際大小,這時瀏覽器決定採起一些行動,拋出異常,它是這樣的:

Stack Overflow

在單線程上運行代碼十分簡單,由於不須要處理在多線程環境中遇到的複雜場景——例如,死鎖。

但單線程上的代碼運行也至關受限。因爲 JavaScript 只有單一的調用棧,當運行很是慢時會發生什麼呢?

併發和事件循環

當調用棧中存在大量耗時才能處理的函數時會發生什麼?例如,假設你須要在瀏覽器中使用 JavaScript 執行某些很是複雜的圖像轉換。

你也許會問——這有什麼問題?問題在於當調用棧中有函數等待執行時,瀏覽器實際上沒法作其餘事情——它被阻塞了。這意味着瀏覽器沒法繼續渲染,也不能運行其餘代碼,它只是卡住了。若是你但願擁有流暢的用戶體驗,這就成了問題。

這並非惟一的問題。一旦你的瀏覽器開始執行棧裏如此之多的任務,它可能會在至關長的時間裏暫停響應。大多數瀏覽器會採起報錯的行爲,詢問你是否要關閉頁面。

Page Unresponsive

這可不是最好的用戶體驗,不是嗎?

那麼,咱們要怎樣在既不阻塞 UI 又不致使瀏覽器無響應的狀況下執行大量的代碼呢?解決方案是:異步回調

這將在《JavaScript工做原理》教程的第二部分詳細解釋。

相關文章
相關標籤/搜索