1. 程式人生 > >呼叫堆疊

呼叫堆疊

JavaScript 引擎是什麼?

JavaScript 引擎說起來最流行的當然是谷歌的 V8 引擎了, V8 引擎使用在 Chrome 以及 Node 中,下面有個簡單的圖能說明他們的關係:
在這裡插入圖片描述

這個引擎主要由兩部分組成:

  1. 記憶體堆:這是記憶體分配發生的地方
  2. 呼叫棧:這是你的程式碼執行時的地方

引擎怎麼執行?

在這裡插入圖片描述

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

然後我們還擁有如此流行的事件迴圈和回撥佇列。


為什麼要知道堆疊?

如果一個專案越來越依賴 JavaScript,這就意味著開發人員必須利用這些語言和生態系統提供更深層次的核心內容去構建一個令人振奮的應用。然而,事實證明,有很多的開發者每天都在使用 JavaScript,但是卻不知道在底層 JavaScript 是怎麼運作的。


什麼是呼叫堆疊?

JavaScript 是一門單執行緒的語言,這意味著它只有一個呼叫棧,因此,它同一時間只能做一件事。

一個呼叫堆疊 是一個解釋的機制(如在Web瀏覽器中的JavaScript直譯器)

呼叫棧是一種資料結構,它記錄了我們在程式中的位置。如果我們執行到一個函式,它就會將其放置到棧頂。當從這個函式返回的時候,就會將這個函式從棧頂彈出,這就是呼叫棧做的事情。

  • 當指令碼呼叫函式時,直譯器將其新增到呼叫堆疊,然後開始執行該函式。
  • 該函式呼叫的任何函式都會進一步新增到呼叫堆疊中,並在到達其呼叫的位置執行。
  • 當前函式完成後,直譯器將其從堆疊中取出並在最後一個程式碼清單中從中斷處繼續執行。
  • 如果堆疊佔用的空間超過分配給它的空間,則會導致“堆疊溢位”錯誤。

(1)例一:

function greeting() {
   // [1] Some codes here
   sayHi();
   // [2] Some codes here
}
function sayHi() {
   return "Hi!";
}

// Invoke the `greeting` function
greeting();

// [3] Some codes here
  1. 忽略所有功能,直到它到達greeting()功能。

  2. 呼叫該greeting()功能。

  3. greeting

    函式新增到呼叫堆疊列表中。

    呼叫堆疊列表:
    greeting

  4. 執行greeting函式中的所有程式碼行。

  5. 開始使用該sayHi()功能。

  6. 將該sayHi()函式新增到呼叫堆疊列表中。

    呼叫堆疊列表:
    greeting
    sayHi

  7. 執行sayHi()函式內的所有程式碼行,直到結束。

  8. 將執行返回到呼叫的行sayHi()並繼續執行greeting()函式的其餘部分。

  9. sayHi()從我們的呼叫堆疊列表中刪除該函式。

    呼叫堆疊列表:
    greeting

  10. 當greeting()函式內部的所有內容都被執行後,返回其呼叫行繼續執行其餘的JS程式碼。

  11. greeting()從呼叫堆疊列表中刪除該函式。

    呼叫堆疊列表:
    EMPTY

我們從一個空的呼叫堆疊開始,每當我們呼叫一個函式時,它會自動新增到呼叫堆疊中,在執行所有程式碼後,它會自動從呼叫堆疊中刪除。最後,我們最終得到了一個空堆疊。


(2)例二:
在這裡插入圖片描述


什麼是堆疊溢位?

“堆疊溢位”,當你達到呼叫棧最大的大小的時候就會發生這種情況,而且這相當容易發生,特別是在你寫遞迴的時候卻沒有全方位的測試它。

例如:

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

當我們的引擎開始執行這段程式碼的時候,它從 foo 函式開始。然後這是個遞迴的函式,並且在沒有任何的終止條件的情況下開始呼叫自己。因此,每執行一步,就會把這個相同的函式一次又一次地新增到呼叫堆疊中。然後它看起來就像是這樣的:
在這裡插入圖片描述

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

什麼是併發與事件迴圈?

呼叫棧中的函式呼叫需要大量的時間來處理,那麼這會發生什麼情況呢?例如,假設你想在瀏覽器中使用 JavaScript 進行一些複雜的圖片轉碼。

事實上,問題是當呼叫棧有函式要執行,瀏覽器就不能做任何事,它會被堵塞住。這意味著瀏覽器不能渲染,不能執行其他的程式碼,它被卡住了。如果你想在應用裡讓 UI 很流暢的話,這就會產生問題。
  而且這不是唯一的問題,一旦你的瀏覽器開始處理呼叫棧中的眾多工,它可能會停止響應相當長一段時間。大多數瀏覽器都會這麼做,報一個錯誤,詢問你是否想終止 web 頁面。
在這裡插入圖片描述


什麼是執行上下文?

簡而言之,執行上下文是評估和執行 JavaScript 程式碼的環境的抽象概念。每當 Javascript 程式碼在執行的時候,它都是在執行上下文中執行。


執行上下文的型別?

全域性執行上下文 — 這是預設或者說基礎的上下文,任何不在函式內部的程式碼都在全域性上下文中。它會執行兩件事:建立一個全域性的 window 物件(瀏覽器的情況下),並且設定 this 的值等於這個全域性物件。一個程式中只會有一個全域性執行上下文。

函式執行上下文 — 每當一個函式被呼叫時, 都會為該函式建立一個新的上下文。每個函式都有它自己的執行上下文,不過是在函式被呼叫時建立的。函式上下文可以有任意多個。每當一個新的執行上下文被建立,它會按定義的順序(將在後文討論)執行一系列步驟。

Eval 函式執行上下文 — 執行在 eval 函式內部的程式碼也會有它屬於自己的執行上下文,但由於 JavaScript 開發者並不經常使用 eval,所以在這裡我不會討論它。