JavaScript 如何工做系列: 引擎、運行時、調用棧概述

譯者: 波比小金剛javascript

翻譯水平有限,若有錯誤請指出。前端

原文: blog.sessionstack.com/how-does-ja…java

ps: 最近開始整理全部的優質文章翻譯集,固然若是你有好的文章請提 issue,我會找時間翻譯出來。git


JavaScript 愈來愈流行,在前端、後端、hybrid apps、嵌入式設備開發等方向上都有它活躍的身影。後端

這篇文章是 How JavaScript Works 系列的開篇,該系列的文章旨在深刻挖掘 JavaScript 及其實際的工做原理。咱們認爲了解 JavaScript 的構建塊及其共同做用,能夠幫助咱們寫出更優雅、更高效的代碼和應用。瀏覽器

正如 GitHut stats 所展現的同樣,JavaScript 各方面的統計數據都是棒棒噠,頂多也就在個別統計項上落後了其餘語言那麼一丟丟。session

GitHut stats

若是項目深度依賴 JavaScript,這意味着開發者須要對底層有極其深刻的瞭解,並利用語言和生態提供的一切東西來構建出色的應用。數據結構

然而,事實上,不少開發者雖然天天都在使用 JavaScript,卻對其背後發生的事情一無所知。多線程

概述

幾乎每一個人都據說過 V8 引擎的概念,大多數人也都知道 JavaScript 是一門單線程語言或者知道它是基於回調隊列的。併發

在這篇文章中,咱們將詳細的介紹這些概念而且解釋 JavaScript 實際的運行方式,經過對這些細節的瞭解,你能夠寫出更好、無阻塞的應用。

若是你是一名 JavaScript 新手,這篇文章將幫助你理解爲何 JavaScript 和其它語言對比起來顯得那麼"奇怪"。

若是你是一名老司機,但願可以爲你帶來一些對 JavaScript 運行時的新思考。

JavaScript 引擎

提及 JavaScript 引擎,不得不提的就是 Google 的 V8 引擎,Chrome 和 Nodejs 內部也是使用的 V8。這裏有一個簡單的視圖:

simplified view for v8

引擎主要包含兩個組件:

  • Memory Heap: 內存分配發生的地方
  • Call Stack: 代碼執行時棧幀的位置

運行時

幾乎全部的開發者都使用過瀏覽器中的 APIs (好比: setTimeout),然而,引擎並不提供這些 API。

那麼,這些 API 從何而來?

事實上,這是一個很複雜的問題。

simplified view for runtime

因此,除了引擎以外還有不少內容,包括咱們調用的瀏覽器提供的 Web APIs,好比:DOM, AJAX, setTimeout 等。

而後,還有大名鼎鼎的事件循環和回調隊列。

調用棧

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);
複製代碼

引擎開始執行這段代碼的時候,調用棧是空的,接着的步驟以下:

call stack 01

對於調用棧中的每個條目,咱們叫作"棧幀"(Stack Frame)

這正是異常拋出時堆棧追蹤的構造方式 - 基本上就是異常發生時調用棧的狀態。

咱們看看以下代碼:

function foo() {
    throw new Error('SessionStack will help you resolve crashes :)');
}

function bar() {
    foo();
}

function start() {
    bar();
}

start();
複製代碼

在瀏覽器執行(假設代碼在 foo.js 文件),能夠在控制檯看到以下堆棧追蹤信息:

call stack 02

"爆棧" - 當達到調用棧的最大大小的時候發生。並且這很容易發生,好比下面的這段牛逼的遞歸調用代碼:

function foo() {
    foo();
}

foo();
複製代碼

當引擎開始執行這段代碼的時候,首先調用函數 "foo",可是這個函數接着遞歸的調用本身,而且沒有終止條件。相同的函數不斷的加到調用棧中,以下:

call stack 03

當調用棧中函數的數量超過其閥值的時候,瀏覽器決定動手了。瀏覽器會拋出一個以下的異常信息!

call stack 04

單線程上運行一個程序,對比在多線程環境下的運行簡單不少,由於不須要處理多線程運行下的一些複雜場景,好比:死鎖。

可是單線程也會很坑的,既然只有一個調用棧,那麼執行一個很慢很慢的計算的時候,你就會崩潰了。

併發與事件循環

若是你的調用棧中存在一個須要大量時間處理的函數的時候,會發生什麼?假如你想在瀏覽器端經過 JavaScript 進行復雜的圖像轉換。

你可能會問 - 這也算是一個問題?問題就是當調用棧有函數在執行的時候,瀏覽器實際上不能作別的任何操做 - 它會被阻止。 這意味着瀏覽器不能渲染,不能執行別的代碼,它被卡住了。若是你須要流暢的 UI 體驗,那就很糟糕了。

這還不是惟一的問題,一旦瀏覽器遇到不少不少的任務須要在調用棧中處理的時候,可能很長的一段時間內會中止響應。這個時候大多數瀏覽器就會採起行動,問你是否須要終止網頁。

event loop 01

這並非最好的用戶體驗,是吧?

因此,咱們如何處理繁重的代碼並且不阻塞渲染或者不使瀏覽器中止響應呢,答案就是異步回調。

這將在本系列文章的第二部分詳細闡述。

相關文章
相關標籤/搜索