JavaScript的工作原理:引擎,執行時和呼叫堆疊

前端先鋒發表於2019-01-16

翻譯:瘋狂的技術宅

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

隨著JavaScript變得越來越流行,越來越多的團隊正在利用他們為技術棧中做多個級別的支援:前端、後端、混合應用、嵌入式裝置等等。

本文旨在深入挖掘JavaScript及其實際的工作方式:我們認為通過了解JavaScript的構建塊以及它們如何發揮作用,你將能夠編寫更好的程式碼和應用。 我們還將分享自己在構建SessionStack時使用的一些經驗和規範,這是一個輕量級JavaScript應用,必須具有強大功能和高效能才能保持競爭力。

正如GitHut stats所示,JavaScript在GitHub中的Active Repositories和Total Pushes方面處於領先地位。 它也不會落後於其他語言。

img

檢視最新的GitHub語言統計資訊)。

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

事實證明,有很多開發人員每天都在使用JavaScript,卻不瞭解背後究竟發生了些什麼。

概述

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

在本文中,我們將詳細介紹這些概念,並解釋JavaScript實際執行的方式。 通過了解這些詳細資訊,你將能夠正確地利用其所提供的API編寫更好的、非阻塞的應用,這些應用正確地利用了所提供的API。

如果你對JavaScript比較陌生,那麼本文將幫助你理解為什麼JavaScript與其他語言相比是如此的“奇怪”。

如果你是一位經驗豐富的JavaScript開發者,儘管你每天使用它,但仍然希望它能夠為你提供一些關於JavaScript執行時工作方式方面的新見解。

JavaScript引擎

一個很流行的JavaScript引擎是Google的V8引擎。 V8引擎被用於Chrome和Node.js。 這是一個非常簡化的示意圖:

img

引擎包含兩個主要元件:

  • 記憶體堆 - 這是進行記憶體分配的地方
  • 呼叫棧 - 這是你的程式碼執行時堆疊幀的位置

執行時

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

那麼,他們究竟來自哪裡?

實際上這有點複雜。

img

所以儘管有了引擎,但是還需要很多東西。有一些叫做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);
複製程式碼

當引擎開始執行上面的程式碼時,呼叫堆疊將為空。 接下來的步驟如下:

img

呼叫棧中的每個條目被稱為棧幀

這是在丟擲異常時堆疊跟蹤的構造方式 —— 當異常發生時呼叫堆疊的大致狀態。 接下來看下面這段程式碼:

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

如果在Chrome中執行這個操作(假設此程式碼位於名為foo.js的檔案中),則將生成以下堆疊跟蹤:

img

當達到最大呼叫堆疊大小時會發生“Blowing the stack”這種情況。 這種情況是很容易發生的,尤其是在你使用遞迴而沒有充分地測試你的程式碼時。 看一下這段程式碼:

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

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

img

在某些時候,如果呼叫棧中的函式呼叫數量超過了它的實際大小,瀏覽器就會丟擲錯誤,該錯誤看起來像這樣:

img

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

但是跑在單個執行緒上也是非常受限的。 由於JavaScript只有一個呼叫,當處理變慢時會發生什麼?

併發和事件迴圈

如果在呼叫堆疊中有需要花費大量時間才能處理的函式呼叫,會發生什麼? 比如假設你想在瀏覽器中用JavaScript進行一些複雜的影象轉換。

你可能會問:這也算是一個問題? 實際上雖然呼叫棧具有執行功能,但瀏覽器實並沒有辦法執行其他的操作,因為它會被阻止。 這意味著瀏覽器將無法進行渲染,也無法執行任何其他程式碼,它只是被卡住了。 如果你想在自己的應用中產生流暢的UI,在這裡將會出現問題。

這並不是唯一的問題。 一旦你的瀏覽器開始在呼叫棧中處理如此之多的任務,它可能會在相當長的時間內停止響應。 大多數瀏覽器將會通過引發錯誤來解決這個問題,詢問你是否要終止網頁的執行。

img

所以這並不是最佳的使用者體驗,對嗎?

那麼怎樣才能在不阻止UI,並使瀏覽器在無響應的情況下執行繁重的程式碼呢? 解決方案是非同步回撥。

這一點在“如何執行JavaScript”教程的第2部分中有更詳細的解釋:“在V8引擎是怎麼工作的:有關如何編寫優化程式碼的5個技巧(blog.sessionstack.com/how-javascr…)”。

與此同時,如果你在JavaScript應用程式中遇到難以複製和理解的問題,可以試試SessionStack(www.sessionstack.com/?utm_source…)。 SessionStack會記錄Web應用中所有的內容:所有的DOM修改、使用者互動、JavaScript異常、堆疊跟蹤、網路請求失敗和除錯訊息。

通過SessionStack,你可以將網路應用中的問題重現,並檢視發生的所有事情。

有一個免費的工具,不需要支付任何費用。 現在就可以試試(www.sessionstack.com/solutions/d…)。

img

原文首發於京程一燈公眾號:jingchengyideng

相關文章