1. 程式人生 > >JavaScript是如何工作的:引擎、執行時及呼叫棧概述

JavaScript是如何工作的:引擎、執行時及呼叫棧概述

隨著JavaScript越來越流行,團隊在多個層面對其進行了支援 - 前端、後端、混合應用程式、嵌入式裝置等等。

本文是系列文章的第一篇,旨在深入研究JavaScript及其工作方式:我們認為通過了解JavaScript的構建塊以及它們如何工作,您將能夠編寫更好的程式碼和應用。

如GitHut統計中所示,JavaScript在GitHub中的活動程式碼庫和總推送量方面位居前列,在其他類別也不落後。

image

如果專案越來越依賴於JavaScript,這意味著開發人員必須利用語言和生態系統提供的所有內容,深入瞭解內部,從而構建出令人驚歎的軟體。

事實證明,有很多開發人員每天都在使用JavaScript,但並不知道底層會發生什麼。

概述

幾乎每個人都已經聽說過V8引擎這個概念,大多數人都知道JavaScript是單執行緒的,或者它使用回撥佇列。

在這篇文章中,我們將詳細介紹所有這些概念,並解釋JavaScript是如何執行的。 通過了解這些細節,您將能夠編寫更好的、非阻塞的應用程式,正確利用所提供的API。

如果您對JavaScript比較陌生,這篇文章將幫助您理解為什麼JavaScript與其他語言相比比較“怪異”。

如果您是一位經驗豐富的JavaScript開發人員,希望這篇文章能夠為您提供一些對於JavaScript執行時是如何工作的全新見解。

JavaScript引擎

一個流行的JavaScript引擎的例子是Google的V8引擎。 例如,在Chrome和Node.js中使用的就是V8引擎。 下面是一個簡化了的V8檢視:

image

引擎由兩個主要元件組成:

  • 記憶體堆 - 這是記憶體分配發生的地方
  • 呼叫堆疊 - 這是程式碼執行時的堆疊幀

執行時

瀏覽器中有幾乎所有的JavaScript開發者都使用過API(例如“setTimeout”)。 但是,這些API不是由引擎提供的。

那麼,他們從哪裡來?

現實世界有點複雜。

image

所以,除了引擎,實際上還有更多,例如那些被瀏覽器提供的稱為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);

當引擎開始執行這個程式碼時,呼叫棧是空的。 之後,步驟如下:

image

呼叫堆疊中的每個條目稱為堆疊幀。

這正說明了丟擲異常時堆疊如何構建的 - 這基本上是異常發生時的呼叫堆疊的狀態。 看看下面的程式碼:

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

如果這段程式碼在Chrome中執行(假設這個程式碼在一個名為foo.js的檔案中),那麼會產生下面的呼叫棧:

image

“堆疊溢位” - 當達到最大呼叫堆疊大小時會發生這種情況。 這會很容易發生,特別是如果您使用遞迴且沒有廣泛地測試程式碼的時候。 看看這個示例程式碼:

function foo() {
    foo();
}
foo();

當引擎開始執行這個程式碼時,它首先呼叫函式“foo”。 然而,這個函式是遞迴的,並且呼叫它自己而沒有任何終止條件。 所以在執行的每個步驟中,同一個函式會一次又一次地新增到呼叫堆疊中。 它看起來像這樣:

image

然而,在某些情況下,呼叫堆疊中某個函式呼叫的數量超出了呼叫堆疊的實際大小,瀏覽器通過丟擲一個錯誤(如下所示)來決定如何處理:

image

在單執行緒上執行程式碼可能非常容易,因為您不必處理多執行緒環境中出現的複雜場景,例如死鎖。

但是在單執行緒上執行也有侷限。 由於JavaScript只有一個呼叫棧,當某個事務處理很慢時會發生什麼?

併發 & 事件迴圈

如果在呼叫堆疊中進行函式呼叫需要花費大量時間,會發生什麼? 例如,在瀏覽器中使用JavaScript進行一些複雜的影象轉換。

你可能會問 - 為什麼這是一個問題? 問題是,雖然呼叫堆疊在執行函式,但瀏覽器實際上不能做任何其它的事情 - 它被阻塞了。 這意味著瀏覽器無法渲染,也不能執行任何其他程式碼,它只是卡住了。 如果你想在應用程式中有著流暢的UI,這會產生問題。

這不是唯一的問題。 一旦您的瀏覽器開始在呼叫堆疊中處理如此多的任務,它可能會停止響應相當長的時間。 而且大多數瀏覽器通過引發錯誤來採取行動,詢問您是否要終止網頁。

image

現在,這個使用者體驗很糟糕,不是嗎?

那麼,我們如何執行密集運算程式碼而不會阻塞UI並使瀏覽器無法響應? 解決方案是非同步回撥。

這將在“JavaScript是如何工作的”教程的第2部分中更詳細地闡述:“V8引擎內部+關於如何編寫優化程式碼的5個技巧”。

image