JavaScript的工作原理:引擎,運行時和調用堆棧的概述

原文地址:https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf

隨着 JavaScript 變得越來越流行,各團隊正在多個領域棧中使用它們,其中包括 — 前端,後端,混合應用,嵌入式等等。

這篇文章是系列中的第一篇,旨在深入挖掘 JavaScript 及其實際工作原理:通過了解 JavaScript 的構建塊(building blocks of JavaScript)以及它們如何共同發揮作用,您將能夠編寫更好的代碼和應用。我們還將分享我們在構建 SessionStack 時使用的一些經驗法則,這是一個輕量級 JavaScript 應用程序,必須具有強大且高性能才能保持競爭力。

GitHut stats 統計顯示,JavaScript 在活躍倉庫數量以及提交數量上處於領先地位。其他方面也並不落後很多(截止到19年2季度,JavaScript 已在各個指標上領先於其他語言)。

可查看:https://madnight.github.io/githut/#/pull_requests/2019/2
githut.png
如果項目越來越依賴於 JavaScript,這意味着開發人員必須利用語言和生態系統提供的內容,同時需要對內部進行更深入的瞭解,以便構建出色的應用。

事實證明,有很多開發人員每天都在使用 JavaScript,但卻不瞭解幕後發生的事情(對JavaScript 是如何工作的原理卻知之甚少)。

Overview

大多數人已經聽說過 V8引擎的概念,我們知道 JavaScript 是單線程的,其使用的是回調隊列(callback queue)。

這篇文章,我們將詳細介紹這些概念以及 JavaScript 實際運行方式。通過了解這些細節,你將能夠編寫更加健壯,以及正確利用所提的API的非阻塞的應用程序。

如果您對 JavaScript 比較陌生(新手),這篇文章將幫助你理解爲什麼 JavaScript 與其他語言比起來是如此的“驚豔(weird)”。

如果您是一個經驗豐富的 JavaScript 的開發者,希望它會給您帶來一些關於您每天工作使用的 Javascript Runtime 的新見解。

The JavaScript Engine

Google’s V8 是流行的 JavaScript 引擎之一。V8 引擎用於 Chrome 和 Nodejs。這是一個簡化版的視圖:
在這裏插入圖片描述
引擎有兩個重要組成部分:

  • Memory Heap  — 內存分配發生的地方
  • Call Stack  — 代碼執行時堆棧幀(stack frames)的位置

The Runtime

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

那麼,他們來自哪裏?
事實證明,它們來歷有點複雜。
js-runtime.png
除了引擎,實際上還有很多其他東西。這些由瀏覽器提供的我們統稱爲 Web API,如 DOM, AJAX, setTimeout 等等。

接下來,我們將介紹一下非常流行的 事件循環(event loop)回調隊列(callback queue)

The Call Stack

JavaScript 是單線程編程語言,這意味着它只有一個Call Stack。因此它在某一時刻只能做一件事情。

調用棧(Call Stack)是一種數據結構,它主要是記錄 JavaScript 整個執行過程。如果我們執行一個函數,我們將把它放在棧的頂部(壓棧);如果函數返回,會彈出堆棧的頂部(出棧)。這一切都是堆棧可以做到的。

我們來看一個例子吧。看看下面的代碼:

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

當引擎開始執行此代碼時,Call Stack 爲空。
之後,步驟如下:
在這裏插入圖片描述
調用棧中的每個條目稱爲堆棧幀(Stack Frame)

這正是拋出異常時堆棧跟蹤的構造方式 - 它基本上是異常發生時調用棧的狀態(異常後的全過程)。看看下面的代碼:

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

如果在Chrome中執行此操作(假設此代碼位於名爲foo.js的文件中),則將生成以下堆棧跟蹤記錄:
js-throw-stack
堆棧溢出(Blowing the stack)” — 當達到最大調用堆棧大小時會發生這種情況(Javascript引擎產生的堆棧超過 Javascript 運行環境所提供的最大數量)。如果你使用沒有設置結束條件的遞歸時,很容易產生。看看這個示例代碼:

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

當引擎開始執行此代碼時,它首先調用函數“foo”。但是,此函數是遞歸的,並且在沒有任何終止條件的情況下開始調用自身(產生無限循環)。因此,在執行的每個步驟中,相同的函數會一遍又一遍地添加到調用堆棧中。它看起來像這樣:
在這裏插入圖片描述
然而,在某些時候,調用堆棧中的函數調用數量超過了調用堆棧的實際大小,瀏覽器會拋出看起來像這樣的錯誤:
在這裏插入圖片描述
在單個線程上運行代碼非常簡單,因爲您不必處理多線程環境中出現的複雜場景 - 例如,死鎖。
但是單線程運行也是受限的。由於 JavaScript 只有一個 Call Stack,what happens when things are slow?

Concurrency & the Event Loop

如果在調用堆棧中有函數調用需要花費大量時間才能處理,會發生什麼?例如,在瀏覽器中使用 JavaScript 進行一些複雜的圖像轉換。

你可能會問 - 這是問題嗎?問題是,當 Call Stack 有待執行的函數時,瀏覽器實際上無法執行任何其他操作 - 它會被阻塞。這意味着瀏覽器無法渲染,無法運行任何其他代碼,它被卡住了。如果您想在應用中使用流暢的UI,這會產生問題。

這不是唯一的問題。一旦 Call Stack 中等待執行的任務很多時,它可能會在相當長的時間內停止響應。大多數瀏覽器都會拋出一個提示信息,徵求你您是否要關閉網頁。
在這裏插入圖片描述
這樣必然將導致非常差的用戶體驗。
那麼,我們如何在不阻塞UI並使瀏覽器無響應的情況下執行繁重的代碼呢好吧,這裏我就不賣關子了,解決方案是異步回調(asynchronous callbacks)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章