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

翻譯:瘋狂的技術宅javascript

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

隨着JavaScript變得愈來愈流行,愈來愈多的團隊正在利用他們爲技術棧中作多個級別的支持:前端、後端、混合應用、嵌入式設備等等。java

本文旨在深刻挖掘JavaScript及其實際的工做方式:咱們認爲經過了解JavaScript的構建塊以及它們如何發揮做用,你將可以編寫更好的代碼和應用。 咱們還將分享本身在構建SessionStack時使用的一些經驗和規範,這是一個輕量級JavaScript應用,必須具備強大功能和高性能才能保持競爭力。git

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

img

查看最新的GitHub語言統計信息)。編程

若是項目愈來愈依賴於JavaScript,這意味着開發人員必須利用語言和其生態系統提供的全部內容,更深刻的瞭解其內部,以便構建出色的軟件。後端

事實證實,有不少開發人員天天都在使用JavaScript,卻不瞭解背後究竟發生了些什麼。瀏覽器

概述

幾乎每一個人都已經據說過V8引擎這個概念,大多數人都知道JavaScript是單線程的,或者它使用的是回調隊列。網絡

在本文中,咱們將詳細介紹這些概念,並解釋JavaScript實際運行的方式。 經過了解這些詳細信息,你將可以正確地利用其所提供的API編寫更好的、非阻塞的應用,這些應用正確地利用了所提供的API。session

若是你對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

相關文章
相關標籤/搜索