前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

通過了解JavaScript的構建塊以及它們是如何工作的,將能夠編寫更好的代碼和應用程序。據 GitHut 統計 數據所示,在GitHub中的活動存儲庫和總推送方面,JavaScript處於頂部。它也不落後於其他類別。

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

如果項目越來越依賴於 JavaScript,這意味著開發人員必須利用語言和生態系統提供的所有內容,對內部進行更深入的瞭解,以便構建出色的軟件。事實證明,有很多開發人員每天都在使用JavaScript,但卻不知道背後發生了什麼。

概述

幾乎每個人都已經聽說過 V8 引擎,大多數人都知道 JavaScript 是單線程的,或者它使用的是回調隊列。本文詳細介紹這些概念,並解釋 JavaScrip 實際如何運行。通過了解這些細節,你將能夠適當地利用所提供的 API 來編寫更好的、非阻塞的應用程序。

JavaScript引擎

JavaScript引擎的一個流行示例是Google的V8引擎。例如,在Chrome和Node.js中使用V8引擎,下面是一個非常簡化的視圖:

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

V8引擎由兩個主要部件組成:

  • Memory Heap(內存堆) — 內存分配地址的地方
  • Call Stack(調用堆棧) — 代碼執行的地方

Runtime(運行時)

有些瀏覽器的 API 經常被使用到(比如說:setTimeout),但是,這些 API 卻不是引擎提供的。那麼,他們是從哪兒來的呢?事實上這裡面實際情況有點複雜。

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

所以說還有很多引擎之外的 API,我們把這些稱為瀏覽器提供 API 稱為 Web API,比如說 DOM、AJAX、setTimeout等等。

然後我們還擁有如此流行的事件循環和回調隊列

調用棧

JavaScript是一種單線程編程語言,這意味著它只有一個調用堆棧。因此,它一次只能做一件事。

調用棧是一種數據結構,它記錄了當前程序中的位置。如果運行到一個函數,它就會將其放置到棧頂,當從這個函數返回的時候,就會將這個函數從棧頂彈出,這就是調用棧做的事情。

來個例子:

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

當程序開始執行的時候,調用棧是空的,然後,步驟如下:

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

每一個進入調用棧的都稱為調用幀

這能清楚的知道當異常發生的時候堆棧追蹤是怎麼被構造的,堆棧的狀態是如何的,讓我們看一下下面的代碼:

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

如果這發生在 Chrome 裡(假設這段代碼是在一個名為 foo.js 的文件中),那麼將會生成以下的堆棧追蹤:

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

"堆棧溢出",當你達到調用棧最大的大小的時候就會發生這種情況,而且這相當容易發生,特別是在你寫遞歸的時候卻沒有全方位的測試它。我們來看看下面的代碼:

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

當引擎開始執行這段代碼時,它首先調用函數“foo”。然而,這個函數是遞歸的,並且在沒有任何終止條件的情況下開始調用自己。因此,在執行的每一步中,相同的函數都會被一次又一次地添加到調用堆棧中,如下所示:

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

然而,在某些時候,調用堆棧中的函數調用數量超過了調用堆棧的實際大小,瀏覽器決定採取行動,拋出一個錯誤,它可能是這樣的:

前端基礎:JavaScript是如何工作的?聊聊引擎,運行時和調用堆棧

在單個線程上運行代碼很容易,因為你不必處理在多線程環境中出現的複雜場景——例如死鎖。

但是在一個線程上運行也非常有限制,由於 JavaScript 只有一個調用堆棧,當某段代碼運行變慢時會發生什麼?

併發與事件循環

當調用堆棧中的函數調用需要花費大量時間來處理時會發生什麼情況? 例如,假設你希望在瀏覽器中使用JavaScript進行一些複雜的圖像轉換。

你可能會問-為什麼這是一個問題?問題是,當調用堆棧有函數要執行時,瀏覽器實際上不能做任何其他事情——它被阻塞了,這意味著瀏覽器不能呈現,它不能運行任何其他代碼,它只是卡住了,如果你想在應用中使用流暢的頁面效果,這就會產生問題。

那麼,怎樣才能在不阻塞UI和不使瀏覽器失去響應的情況下執行大量代碼呢?解決方案是異步回調。


歡迎關注


分享到:


相關文章: