JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

tristan發表於2018-04-25

引擎,執行時,呼叫堆疊

原文請查閱這裡

本系列持續更新中,Github 地址請查閱這裡

這是 JavaScript 工作原理的第一章。本章會對語言引擎,執行時,呼叫棧做一個概述。

隨著 JavaScript 越來越流行,團隊也利用其在他們諸如前端,後端,混合 apps,嵌入裝置以及更多裝置等開發棧中的不同層面的支援。

本章系列的第一章,本系列旨在深入 JavaScript 並理解它是如何執行的:我們認為在瞭解 JavaScript 的構建模組和它們是如何捏合在一起工作之後你將會寫出更好的程式碼和 apps。我們將會分享一些當在建立 SessionStack 時候的經驗法則,SessionStack 是一個輕量級的 JavaScript 程式它擁有強壯性和高效能的優點以保持競爭力。

正如 GitHut stats 所顯示的那樣,JavaScript 的活躍庫和總推送數在 Github 排名第一。其它方面的表現也不會比其它語言落下太多。

JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

(點選檢視最新 Github 語言統計)

如果工程非常依賴於 JavaScript,那麼這意味著開發者不得不使用 JavaScript 和其語言生態提供的一切事物,為了能夠創造出很酷的軟體,就得更加深入地瞭解 JavaScript 語言的內部工作機制。

事實上,有很多開發者在每天日常開發中都會使用 JavaScript 但是卻不瞭解其底層的知識。

概述

幾乎所有人都已經聽說過 V8 引擎的概念,並且很多人知道 JavaScript 是單執行緒的或者說是使用回撥佇列的。

在本章中,我將會詳細地過一下這些概念並解釋 JavaScript 的工作原理。有賴於瞭解這些細節,通過合理地使用提供的 APIs 你將可能寫出更好的,非阻塞的程式。

如果你是新手,本文將會幫助你理解為什麼和其它語言比較 JavaScript 是不可思議的。

如果你是一個經驗豐富的 JavaScript 開發者,但願,它將會讓你更加深入地瞭解 JavaScript 執行時工作原理。

JavaScript 引擎

谷歌 V8 引擎是流行的 JavaScript 引擎之一。V8 引擎在諸如 Chrome 和 Node.js 內部使用。這裡有一個簡單的檢視來描繪其大概。

JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

引擎包括兩個主要元件:

  • 動態記憶體管理 - 在這裡分配記憶體
  • 呼叫棧-這裡程式碼執行即是你的堆疊結構

執行時

幾乎每個 JavaScript 開發者都使用過一些瀏覽器 API(比如 setTimeout)。然而這些 API並不是引擎所提供的。

那麼它們從何而來?

事實上這個情況有點複雜呃。。

JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

所以,除了引擎但是實際上還有更多其它方面的東西。有被稱為 Web API 的東西,這些 Web API 是由瀏覽器提供的,比如 DOM,AJAX,setTimeout 以及其它。

於是乎,就有了流行的事件迴圈和回撥佇列。

呼叫棧

JavaScript 只是一個單執行緒的程式語言,這意味著它只有一個呼叫棧。這樣它只能一次做一件事情。

呼叫棧是一種資料結構,裡面會記錄我們在程式中的大概位置。當執行進入一個函式,把它置於棧的頂部。如果從函式中返回則從棧頂部移除函式。這就是呼叫棧所能夠做的事情。

舉個栗子。檢視如下程式碼:

function multiply(x, y) {
  return x * y;
}

function printSquare(x) {
  var s = multiply(x, x);
  console.log(s);
}

printSquare(5);
複製程式碼

當引擎開始執行這段程式碼的時候,呼叫棧會被清空。之後,產生如下步驟:

JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

呼叫棧中的每個入口被稱為堆疊結構。

當丟擲異常的時候這正好是堆疊追蹤是如何被構造出來的-當發生異常的時候這基本上是呼叫棧的狀態。看下如下程式碼:

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

function bar() {
  foo();
}

function start() {
  bar();
}

start();
複製程式碼

如果在 Chrome 中執行(假設程式碼在 foo.js 的檔案中),將會產生如下的堆疊追蹤:

JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

"堆疊溢位"-當你達到最大呼叫棧大小的時候發生。這種情況相當容易發生,特別是當你使用遞迴而沒有仔細地檢查程式碼的時候。檢視下如下程式碼:

function foo() {
  foo();
}

foo();
複製程式碼

當引擎開始執行這段程式碼的時候,它開始呼叫 foo 函式。這個函式,然而,會遞迴併開始呼叫其自身而沒有任何結束條件。所以在每步執行過程中,呼叫堆疊會反覆地新增同樣的函式。執行過程如下所示:

JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

在某一時刻,然而,呼叫堆疊中的函式呼叫次數超過了呼叫堆疊的實際大小,這樣瀏覽器決定丟擲錯誤的動作,如下所示:

JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

在單執行緒中執行程式碼會相當輕鬆因為你不用處理多執行緒環境中產生的一些複雜情況,比如死鎖。

但是在單執行緒執行程式碼也會有相當的限制。由於 JavaScript 只有一個呼叫棧,如果執行很慢會發生什麼?

併發和事件迴圈

當你在呼叫棧中有函式為了完成執行需要消耗大量的時間的時候會發生什麼?例如,想象一下你想要在瀏覽器用 JavaScript 來執行一些複雜的影象轉化。

你或許會問-為什麼這也是個問題?問題是這樣的當呼叫棧有函式需要執行,瀏覽器實際上不能做其它任何事-它被阻塞了。這意味著瀏覽器不能夠執行渲染,它不能夠執行其它程式碼,它卡住了。如果你想要在 app 中擁有酷炫的流暢 UI 體驗,這將會是個問題。

這不會是唯一的問題。一旦瀏覽器開始在呼叫棧中執行如此多的任務,瀏覽器將會在相當一段時間內停止互動。大多數瀏覽器會丟擲一個錯誤,詢問你是否關閉網頁。

JavaScript 工作原理之一-引擎,執行時,呼叫堆疊(譯)

現在,這並不是最好的使用者體驗,難道不是嗎?

因此,如何不阻塞 UI 且不讓瀏覽器停止響應來執行執行緩慢的程式碼呢?使用非同步回撥。

這將會在 『JavaScript 工作原理』 第二章:『在V8 引擎中如何寫最佳程式碼的 5 條小技巧』中進行詳細闡述。

打個廣告 ^.^

今日頭條招人啦!傳送簡歷到 likun.liyuk@bytedance.com,即可走快速內推通道,長期有效!國際化PGC部門的JD如下:c.xiumi.us/board/v5/2H…,也可內推其他部門!

本系列持續更新中,Github 地址請查閱這裡

相關文章