首頁 >web前端 >js教程 >JavaScript 執行上下文 – JS 程式碼如何在幕後運行

JavaScript 執行上下文 – JS 程式碼如何在幕後運行

Mary-Kate Olsen
Mary-Kate Olsen原創
2025-01-05 04:47:39286瀏覽

在了解什麼是 JavaScript 執行上下文之前,我們需要知道如何以及在哪些環境中執行 JavaScript 程式碼。

首先,我們可以在兩種環境下執行JavaScript:

  1. 透過瀏覽器
  2. 透過 Node.js

JavaScript 程式碼如何在我們的電腦上運作?

當我們在電腦上編寫 JavaScript 程式碼然後嘗試執行它時,程式碼首先會轉到瀏覽器或 Node.js。

但是,我們寫的 JavaScript 程式碼並不能被瀏覽器或 Node.js 直接理解。此時,兩者都將程式碼傳送到內建的 JavaScript 引擎。有不同類型的引擎,例如:

  1. Google Chrome 中的 V8 引擎,
  2. Mozilla Firefox 中的SpiderMonkey,
  3. Node.js 中的 V8 引擎等

接下來,JavaScript 引擎將 JavaScript 程式碼編譯為機器碼。然後,該機器代碼被傳送到計算機,計算機執行它,我們會看到顯示的輸出。

身為程式設計師,我們需要很好地理解這個中間步驟,即 JavaScript 引擎如何將 JavaScript 程式碼編譯為機器碼。

所以,現在我們需要了解 JavaScript 引擎是如何運作的。 JavaScript 引擎以兩種方式將程式碼轉換為機器碼。第一個是解釋,第二個是編譯。那麼,什麼是解釋和編譯呢?

什麼是口譯,它是如何運作的?

解釋是逐行讀取所有用高階語言編寫的原始程式碼,並在讀取後立即將每一行轉換為機器碼的過程。如果在讀取一行程式碼時出現錯誤,則該過程會立即停止,使程式設計師可以輕鬆識別錯誤。這使得調試變得簡單。不過,由於這個過程是逐行讀取程式碼,所以速度相對較慢。

什麼是編譯,它是如何運作的?

編譯是將所有用高階語言寫的原始碼一次轉換為機器碼的過程。在這種情況下,即使程式碼中有錯誤,它仍然會編譯並且只在執行時顯示錯誤。結果,程式設計師更難識別錯誤,從而使偵錯變得更具挑戰性。然而,由於整個原始碼立即轉換為機器碼,因此這個過程相對更快。那麼現在問題來了:JavaScript 是編譯語言還是解譯型語言?

JavaScript 是編譯型語言還是解釋型語言?

最初,JavaScript 主要被認為是一種解釋性語言。然而,由於這個過程非常緩慢,現代 JavaScript 引擎開始使用一種結合解釋和編譯的新技術,稱為即時 (JIT) 編譯。這個過程結合了解釋和編譯,將程式碼轉換為機器碼。因此,與舊方法相比,它的調試速度更快、更容易。

要了解 JavaScript 的即時 (JIT) 編譯如何運作,我們需要了解 JavaScript 的執行上下文。現在讓我們試著了解 JavaScript 的執行上下文。

JavaScript 執行上下文

首先,看一下下面的程式碼範例。

程式碼範例

var a = 1;

function one() {
  console.log(a);

  function two() {
    console.log(b);

    var b = 2;

    function three(c) {
      console.log(a + b + c);
    }

    three(4);
  }

  two();
}

one();

輸出

1
undefined
7

當我們執行程式碼時,我們嘗試在two()函數內宣告b變數之前列印它,但輸出是未定義的。然而,沒有發生錯誤。問題出現了:b 變數的值是如何未定義的?答案就在 JavaScript 執行上下文。現在,我們將更詳細地探討 JavaScript 執行上下文。

JavaScript 中有兩種類型的執行上下文:

  1. 全域執行上下文
  2. 函數執行上下文

每個執行上下文都會經歷兩個階段:建立階段和執行階段。

全域執行上下文

當我們執行 JavaScript 程式碼時,首先發生的是全域執行上下文。這個上下文首先經歷它的創建階段,其中發生了幾件事:

創建階段

  1. 建立了一個全域物件。
  2. 建立此物件並為其指派全域物件的值。
  3. 創建了一個變數對象,其中聲明了所有函數和變數。變數被指派為未定義的值,函數被指派對其各自函數的參考。

建立階段完成後,全域執行上下文將進入下一個階段:執行階段,其中會發生更多步驟。

執行階段

  1. 在建立階段宣告並使用 undefined 初始化的變數現在被指派了各自的值。
  2. 在建立階段聲明的函數(儲存為引用)現在被呼叫並執行。

函數執行上下文

當全域執行上下文的執行階段所引用的函數被呼叫時,每個函數都會建立自己的函數執行上下文。就像全域執行上下文一樣,函數執行上下文也經歷創建階段,其中發生幾個步驟:

創建階段

  1. 為函數建立參數物件。
  2. 建立此物件並為其指派全域物件的值。
  3. 創建了一個變數對象,其中聲明了所有函數和變數。變數被指派為未定義的值,函數被指派對其各自函數的參考。

建立階段完成後,函數執行上下文將進入執行階段,其中會發生更多步驟。

執行階段

  1. 在創建階段聲明的變量,之前用 undefined 初始化,現在被分配了各自的值。
  2. 在建立階段宣告的函數現在被呼叫並執行。

巢狀函數中的函數執行上下文

當在其他函數中呼叫函數時,將為每個函數建立一個新的函數執行上下文。然後,每個函數執行上下文都會經歷創建階段和執行階段。對於在另一個函數內調用的每個函數,此過程都會繼續,並且每個函數將分別經歷這些階段。

我們看下圖。

JavaScript Execution Context – How JS Code Runs Behind the Scenes

我們已經看到全域執行上下文和函數執行上下文都會經歷一定的步驟。唯一的區別是,在全域執行上下文中,第一步是建立全域對象,而在函數執行上下文中,第一步是為函數建立參數對象。

現在,問題出現了:當為全域上下文和每個函數建立這些執行上下文時,JavaScript 如何管理這些執行上下文?

使用執行堆疊管理執行上下文

為了管理這些上下文,JavaScript 使用一種稱為執行堆疊的資料結構。執行堆疊以類似堆疊的方式儲存上下文:首先是全域執行上下文,然後是每個函數執行上下文。當所有執行上下文都儲存在堆疊中時,JavaScript 從堆疊頂部開始一一處理它們。

使用 let 和 const 來確定作用域

要注意的是,當我們在全域或函數作用域內使用 let 或 const 宣告變數時,這些變數在建立階段不會儲存在變數物件中,也不會使用 undefined 進行初始化。相反,這些變數是在執行階段直接聲明並賦值的。

考慮以下程式碼範例:

程式碼範例

var a = 1;

function one() {
  console.log(a);

  function two() {
    console.log(b);

    var b = 2;

    function three(c) {
      console.log(a + b + c);
    }

    three(4);
  }

  two();
}

one();

如果我們運行這段程式碼,我們將會遇到一個ReferenceError。這是因為我們試圖在宣告 b 變數之前列印它的值,並且由於 b 是使用 const 宣告的,因此它的行為與常規變數不同。使用 const 或 let 宣告的變數在建立階段不會儲存在變數物件中,這就是為什麼在為它們賦值之前嘗試存取它們時會出現錯誤。

結論

我希望對 JavaScript 如何運作以及其執行上下文階段發生的情況的解釋能讓您有更清晰的理解。在下一課中,我們將探討另一個 JavaScript 主題。

您可以在 GitHub 和 Linkedin 上與我聯絡。

以上是JavaScript 執行上下文 – JS 程式碼如何在幕後運行的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn