JavaScript 之執行上下文

南波發表於2018-10-16

本文共 1090 字,讀完只需 4 分鐘

概述

JavaScript 是函數語言程式設計語言,作用域也是以函式為單位,那麼,這些函式程式碼塊是怎麼樣的順序進行的呢, JS 的可執行程式碼又分為 3 種,不同型別的程式碼有不一樣的執行環境。本文就梳理有關 JS 執行上下文(execution context),也叫執行環境的知識。

活動的執行程式碼的上下文在語言底層邏輯上構成一個執行上下文棧(excution context stack),我們知道,棧有兩個行為,壓入棧和彈出棧,並且有後進先出的特點, 最先進入的項會在棧底最後彈出。

不同執行環境的有其相應的變數物件(Variable Object),某個執行環境的所有可執行程式碼都執行完畢後,該環境中的變數物件和函式定義會被清除。

函式程式碼會在執行完後清除變數佔用的記憶體,全域性程式碼則會在關閉環境,比如關閉瀏覽器後清除。

一、程式碼型別

JS 中可執行的程式碼可分為三種型別:

  1. 全域性程式碼
  2. 函式程式碼
  3. Eval 程式碼

全域性程式碼

在 web 瀏覽器中,全域性執行環境是 window 物件,所有的全域性變數和函式都是作為 window 的屬性和方法存在。

全域性程式碼的執行上下文棧可以表示為:

ECStack = [
    globalContext
]
複製程式碼

函式程式碼

當執行函式程式碼時,函式程式碼上下文被壓入到執行上下文棧中。函式程式碼的執行環境中,有自己的內部的定義的變數和宣告。

function foo1() {
    var name1 = "a";
    console.log(name1)
}

function foo2(){
    var name2 = "b";
    console.log(name2)
}

foo1();
foo2();
複製程式碼

上面的執行上下文可以表示為:

ECStack = [
    globalContext
];

ECStack.push(<foo1> functionContext);
ECStack.pop();
ECStack.push(<foo2> functionContext);
ECStack.pop();
複製程式碼

一個函式,可能有多個執行上下文,每個函式的呼叫都會產生新的上下文。

function foo() {
    ...
}

foo("a");
foo("b");
foo("c");
複製程式碼

eval 程式碼

eval 關鍵字接受一個字串作為引數,並將其作為書寫文字上下文的程式碼。

function foo(str, a) {
    eval( str );  // 宣告一個新變數
    console.log(a, b);
}
var b = 123;

foo("var b = 456 ", 123) // 123, 456
複製程式碼

以上程式碼引用《你不知道的 JavaScript》的例子,eval 函式中的字串,被當做可執行程式碼,最後在 foo 函式中宣告瞭 b 變數,並覆蓋了 foo 函式外部的 b 變數。

eval 函式和 JS 的詞法作用域的行為,會造成變數環境和執行上下文的混亂,儘量別使用它。

二、執行上下文棧

前面其實已經提到很多關於執行上下文棧的內容,簡而言之,JS 在遇到全域性程式碼,函式程式碼,eval 程式碼時,會建立相應的執行上下文,不同的上下文,有其變數物件(variable object: VO)和作用域。

看這一段程式碼:

function func3() {
    console.log('fun3')
}

function func2() {
    func3();
}

function func1() {
    func2();
}

func1();
複製程式碼

以上程式碼引用自冴羽的github,程式碼順序用執行上下文棧來表示就是:

ECStack.push(globalContext);

// 呼叫 func1(), 進入 func1 執行環境
ECStack.push(<func1> functionContext);

// func1中呼叫了 func2,進入 func2 的執行上下文
ECStack.push(<func2> functionContext);

// func2 呼叫 func3, 進入 func3 的執行上下文
ECStack.push(<func3> functionContext);

// func3 執行完畢,退出 func3 執行環境
ECStack.pop();

// func2 執行完畢,退出 func2 執行環境
ECStack.pop();

// func1 執行完畢,退出 func1 執行環境
ECStack.pop();
複製程式碼

函式程式碼執行完畢後,執行上下文棧底只剩下全域性程式碼上下文 globalContext, 直到關閉瀏覽器才會徹底清空執行上下文棧。

總結

程式碼在執行時會建立由不同作用域構成的作用域鏈作用域鏈保證 JS 中的變數和函式都能夠有序地訪問和執行。

如果是函式程式碼,則將其 活動物件 作為變數物件,活動物件最開始時只包含一個物件,即 arguments 物件。

後面的文章會介紹變數物件(VO)和作用域鏈的具體內容,敬請期待吧。

部落格內容源自個人公眾號,專注分享原創文章,歡迎關注。

JavaScript 之執行上下文

掘金專欄 JavaScript 系列文章

  1. JavaScript之變數及作用域
  2. JavaScript之宣告提升
  3. JavaScript之執行上下文
  4. JavaScript之變數物件
  5. JavaScript原型與原型鏈
  6. JavaScript之作用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中徹底理解this
  12. JavaScript專題之模擬實現call和apply

相關文章