【譯】JavaScript 運行原理(一):引擎,運行時,調用棧的概述

原文鏈接 How JavaScript works: an overview of the engine, the runtime, and the call stack by Alexander Zlatkovjavascript

這個JavaScript 運行原理的第一章,一共18章,會後續持續更新,盡情期待,也能夠關注一下,防止迷路前端


因爲JavaScript愈來愈流行,各個團隊都在許多不一樣的技術棧中使用它—前端,後端,混合APPs,嵌入式設備等等。java

這篇文章是整個系列中的第一篇,旨在深刻探討Javascript和它的運行原理:咱們認爲,經過了解JavaScript的組成部分以及它們如何一塊兒發揮做用,你可以編寫出更好的代碼和應用。咱們還將分享在構建SessionStack(做者所在的公司)時使用的一些經驗法則,SessionStack是一種輕量級JavaScript應用程序,必須強大且高性能才能保持競爭力。git

如同GitHut stats展現的同樣,就活動資源倉庫和GitHub中的Push總數而言,JavaScript排名第一。在其餘類別中也沒有落後太多。web

若是項目愈來愈依賴JavaScript,則意味着開發人員必須利用語言和生態系統提供的全部內容,並對內部進行愈來愈深刻的瞭解,才能構建出色的軟件。chrome

可是事實證實,有不少開發人員天天都在使用JavaScript,但並不瞭解其內部原理。編程

概述

幾乎每一個人都已經據說過V8引擎,並且大多數人都知道JavaScript是單線程的,或者它正在使用回調隊列。後端

在本文中,咱們將詳細介紹全部這些概念,並解釋JavaScript實際如何運行。經過了解這些詳細信息,可以編寫更好地,無阻塞的應用程序,這些應用程序能夠正確利用提供的API。瀏覽器

若是你是JavaScript的新手,此博客文章將幫助您瞭解JavaScript與其餘語言相比爲什麼如此「古怪」。安全

若是你是一位經驗豐富的JavaScript開發人員,但願它會爲你提供你天天使用的JavaScript運行時實際工做方式的新看法。

一. JavaScript 引擎

一個流行的JavaScript引擎是Google的V8,V8被用於chrome和Node.js中。舉個例子,下圖是V8的一個簡單視圖:

引擎包含兩個主要的組件:

  • 堆內存(Memory Heap)— 內存分配的地方
  • 調用棧 — 這是代碼執行時堆棧幀所在的位置

二. 運行時

瀏覽器中有許多API幾乎全部的JavaScript開發人員都會用到(例如:"setTimeout")。這些API都不是JavaScript引擎提供的。

所以,他們從哪裏來的?

事實證實,現實狀況有點複雜。

所以,雖然咱們有引擎,可是還有不少其餘未知的內容。咱們稱之爲Web API, 由瀏覽器提供,例如DOM,AJAX,setTimeout 等等。

固然,咱們還有很是流行的事件循環(event loop)和回調隊列(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」。可是這個函數是遞歸的,它開始調用本身並且沒有終止條件。所以每一步的執行都是這個函數一遍一遍的往棧裏面添加本身。它像下面這樣:

在某個時間點,調用棧中的函數執行達到了調用棧中的最大值,瀏覽器決定經過拋出異常,像下文同樣:
在單線程模式運行代碼很是的容易,由於你不用去處理那些在多線程環境下的複雜狀況,好比死鎖。

單線程模式也一樣有一些限制,Javascript 只有一個調用棧,當調用棧上的當前代碼執行的時間很長怎麼辦?

併發 & 事件循環(Concurrency & 事件循環)

當你的函數在調用棧中爲了處理某個任務消耗了大量的時間怎麼辦?想象一下,你須要在瀏覽器中經過JavaScript轉換某些複雜的圖片。

你也許會問,爲何這是一個問題?這個問題關鍵在於當調用棧在執行的時候,瀏覽器不能執行其的任務—它被阻塞了。這也意味着瀏覽器不能渲染,不能運行其餘任務代碼,它就被卡在那。若是你但願的是一個流程的web應用,這將是一個問題。

這還不是惟一的問題。一旦瀏覽器開始處理「調用堆棧」中的許多任務,它可能會在至關長的時間內中止響應。並且大多數瀏覽器採起的方案都是拋出錯誤,詢問您是否要終止網頁。

這不是最好的用戶體驗,對嗎?

所以咱們如何執行耗時的代碼而不去阻塞UI和致使瀏覽器不響應?方案是異步回調。

這個會在「JavaScript 運行原理」系列的第二章「深刻了解V8引擎 & 如何寫出最優代碼的5個提示」中進行講解。

本系列其餘文章

  1. 關於引擎,運行時,調用棧的概述
  2. 深刻了解V8引擎 & 如何寫出最優代碼的5個提示
  3. 內存管理 & 如何處理4種常見的內存泄露
  4. 事件循環機制和異步編程的興起 & 經過async/await更好的編碼的5種方法
  5. 經過SSE深刻了解WebSockets和HTTP2 & 如何選擇正確的路徑
  6. 對比WebAssembly & 爲何某種狀況下它要優於JavaScript
  7. Web Workers的構 & 你須要用都它的5種狀況
  8. Service Workers,它的生命週期和使用案例
  9. Web Push Notifications的機制
  10. 經過MutatioinObserver跟蹤DOM的變化
  11. 渲染引擎和優化技巧
  12. 深刻了解網絡層 & 性能優化和安全性
  13. 理解CSS和JS動畫的內部原理 & 性能優化
  14. 解析,抽象語法樹(ASTs) & 如何優化解析時間
  15. 類和繼承的內部原理 & Babel和TypeScript轉義(transpiling)
  16. Storage 引擎 & 如何選擇合適的存儲API
  17. Shadow Dom 的內部原理 & 如何構建獨立的組件
  18. WebRTC和對等網絡機制
相關文章
相關標籤/搜索