JavaScript如何工作:引擎,執行環境和呼叫棧概述
- Home
- Programming >Front end >Javascript
- 【翻譯】JavaScript如何工作:引擎,執行環境和呼叫棧概述
文章目錄
前言
這是一篇翻譯,在查setInterval
的瀏覽器如何處理的過程中看到這一系列文章,感覺對自己理解JS引擎
以及執行環境的工作細節有很大的幫助,決定翻譯一下這一系列文章。英文水平非常爛,只能作為自己的一項練習了,雖然之前也寫了瀏覽器渲染過程和JS
引擎淺析,但是對很多細節理解的還不夠,翻譯這個系列文章應該能讓我的理解更透徹和全面。
原文地址How JavaScript works: an overview of the engine, the runtime, and the call stack
隨著JavaScript
變得越來越流行,很多團隊開始把它運用到不同的層級的技術棧中,前端,後端,混合式應用,嵌入式系統等。
這篇文章是系列中的第一篇,旨在深度挖掘JavaScript
和它的工作原理:我們認為通過了解JavaScript
的構建模組和它們如何協同工作能夠幫助你寫出更好的程式碼和應用。我們也將分享一些我們在開發SessionStack
中的經驗法則,為了保持競爭力而開發的健壯的高效能的輕量JavaScript
應用。
如Githut Stats
所示,JavaScript
擁有GitHub
上最高的活躍倉庫數和最高的總提交數,在其他方面也沒有落後太多。
越來越多的專案開始依賴JavaScript
,也就意味著想要開發出驚豔的軟體就必須深入理解這門語言和JavaScript
生態圈提供的一切。
事實證明,雖然有大量的開發者每天用JavaScript
作為工作但是並不理解內部的工作原理。
概述
幾乎所有人都聽說過V8
引擎的概念,並且大部分人都知道JavaScript
是單執行緒的以及它會用到一個回撥佇列。
在這篇文章中,我們將詳細介紹這些概念並且說明JavaScript
內部的執行機制。通過了解這些細節,你講能夠正確地運用提供的API
寫出更好的非阻塞的應用。
如果你是一個JavaScript
新手,這篇文章能夠幫助你理解JavaScript
相比於其他程式語言的“奇怪”行為。
如果你是個有經驗的JavaScript
程式設計師,相信我,這篇文章會讓你對於JavaScript
執行環境如何工作有全新的理解。
JavaScript
引擎
最流行的JavaScript
引擎是谷歌的V8
引擎,他被用在Chrome
和Node.js
中,他的樣子如下圖所示:
這個引擎由兩個主要部分組成:
1. 記憶體堆:用來分配記憶體。
2. 呼叫棧:程式碼執行棧幀所在位置。
執行環境
瀏覽器提供了許多API
供開發者使用(比如setTimeout
),這些API
並不是由引擎所提供。那麼這些API
到底是哪來的呢?想要說清楚這個有點複雜。
除了引擎我們還有很多其他東西。瀏覽器提供了很多叫做Web APIs
的內容,比如DOM
,AJAX
,setTimeout
等。
之後,我們還有非常受歡迎的事件迴圈(Event Loop
)和回撥佇列 (Callback Queue
)。
呼叫棧The Callback Queue
JavaScript
是一個單執行緒語言,這意味著它只有一個呼叫棧,同一時間只能做一件事。
呼叫棧是一種資料結構,它記錄了我們在程式中的實際位置。當執行流進入一個函式,我們將這個函式壓入呼叫棧頂,當這個函式執行完畢返回,我們將它從棧頂彈出。這就是呼叫棧所做的事情。
我們來看一個例子。看看下面這段程式碼:
function multiply(x, y) { return x * y; } function printSquare(x) { var s = multiply(x, x); console.log(s); } printSquare(5);
當引擎開始執行這段程式碼的時候,呼叫棧是空的,然後執行步驟如下圖所示:
每一進棧的函式都稱為一個棧幀(Stack Frame
)。
當一個異常被丟擲的時候,棧軌跡(Stack Traces
)被建立,從本質上來說這是呼叫棧的狀態。看下面這段程式碼:
function foo() { throw new Error('SessionStack will help you resolve crashes :)'); } function bar() { foo(); } function start() { bar(); } start();
如果這段程式碼在Chrome
中執行(假定程式碼在一個叫做foo.js
的檔案中),將產生下面的棧軌跡。
Blowing the stack
——當你到達棧容量的上限就會發生。這非常容易發生,特別是當你使用遞迴而沒有詳細測試你的程式碼。看看下面這個例子:
function foo() { foo(); } foo();
當引擎開始執行這段程式碼的時候,它先呼叫函式foo
,但是這個函式會無限的呼叫它自身,所以每執行一次,同樣的函式就會被新增到呼叫棧,一直新增到觸發Blowing the stack
。具體情況大概如下圖:
但是,當呼叫棧中的函式呼叫的數量超過呼叫棧的容量的時候,瀏覽器會丟擲一個錯誤,像下圖這樣:
在單個執行緒下執行程式碼是一件很容易的事情,因為你不需要處理多執行緒環境下的複雜情況,比如死鎖。
但是在單執行緒下執行也相當有侷限,因為JavaScript
只有一個呼叫棧,如果有一個任務執行的非常慢該怎麼辦?
並行和事件佇列
如果在呼叫棧中有函式呼叫需要花費大量的時間會發生什麼呢?舉個例子,當你想用JavaScript
在瀏覽器上實現複雜的圖片變形。
你可能會問:這算是一個問題碼?真正的問題是當呼叫棧的函式在執行的時候,瀏覽器什麼也做不了——它被鎖死了。這意味著瀏覽器無法渲染,也無法執行其他程式碼,它卡住了。如果你想讓你的應用擁有流暢的UI
這將是一個大問題。
而且這不是唯一的問題。如果你的瀏覽器開始處理非常多的呼叫棧任務,瀏覽器將開始陷入長時間的未響應狀態。大部分的瀏覽器會採取丟擲錯誤的解決辦法,詢問你是否要終止這個頁面。這將會毀了你產品的使用者體驗。
那麼我們如何執行復雜的程式碼但是不會令UI
卡死,瀏覽器未響應呢?解決辦法是非同步回撥。
關於非同步回撥的內容會在下一片文章詳細介紹。