JavaScript深入之執行上下文

冴羽發表於2017-04-14

JavaScript深入系列第七篇,結合之前所講的四篇文章,以權威指南的demo為例,具體講解當函式執行的時候,執行上下文棧、變數物件、作用域鏈是如何變化的。

前言

《JavaScript深入之執行上下文棧》中講到,當 JavaScript 程式碼執行一段可執行程式碼(executable code)時,會建立對應的執行上下文(execution context)。

對於每個執行上下文,都有三個重要屬性:

  • 變數物件(Variable object,VO)
  • 作用域鏈(Scope chain)
  • this

然後分別在《JavaScript深入之變數物件》《JavaScript深入之作用域鏈》《JavaScript深入之從ECMAScript規範解讀this》中講解了這三個屬性。

閱讀本文前,如果對以上的概念不是很清楚,希望先閱讀這些文章。

因為,這一篇,我們會結合著所有內容,講講執行上下文的具體處理過程。

思考題

《JavaScript深入之詞法作用域和動態作用域》中,提出這樣一道思考題:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();複製程式碼
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();複製程式碼

兩段程式碼都會列印'local scope'。雖然兩段程式碼執行的結果一樣,但是兩段程式碼究竟有哪些不同呢?

緊接著就在下一篇《JavaScript深入之執行上下文棧》中,講到了兩者的區別在於執行上下文棧的變化不一樣,然而,如果是這樣籠統的回答,依然顯得不夠詳細,本篇就會詳細的解析執行上下文棧和執行上下文的具體變化過程。

具體執行分析

我們分析第一段程式碼:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();複製程式碼

執行過程如下:

1.執行全域性程式碼,建立全域性執行上下文,全域性上下文被壓入執行上下文棧

    ECStack = [
        globalContext
    ];複製程式碼

2.全域性上下文初始化

    globalContext = {
        VO: [global, scope, checkscope],
        Scope: [globalContext.VO],
        this: globalContext.VO
    }複製程式碼

2.初始化的同時,checkscope 函式被建立,儲存作用域鏈到函式的內部屬性[[scope]]

    checkscope.[[scope]] = [
      globalContext.VO
    ];複製程式碼

3.執行 checkscope 函式,建立 checkscope 函式執行上下文,checkscope 函式執行上下文被壓入執行上下文棧

    ECStack = [
        checkscopeContext,
        globalContext
    ];複製程式碼

4.checkscope 函式執行上下文初始化:

  1. 複製函式 [[scope]] 屬性建立作用域鏈,
  2. 用 arguments 建立活動物件,
  3. 初始化活動物件,即加入形參、函式宣告、變數宣告,
  4. 將活動物件壓入 checkscope 作用域鏈頂端。

同時 f 函式被建立,儲存作用域鏈到 f 函式的內部屬性[[scope]]

    checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined,
            f: reference to function f(){}
        },
        Scope: [AO, globalContext.VO],
        this: undefined
    }複製程式碼

5.執行 f 函式,建立 f 函式執行上下文,f 函式執行上下文被壓入執行上下文棧

    ECStack = [
        fContext,
        checkscopeContext,
        globalContext
    ];複製程式碼

6.f 函式執行上下文初始化, 以下跟第 4 步相同:

  1. 複製函式 [[scope]] 屬性建立作用域鏈
  2. 用 arguments 建立活動物件
  3. 初始化活動物件,即加入形參、函式宣告、變數宣告
  4. 將活動物件壓入 f 作用域鏈頂端
    fContext = {
        AO: {
            arguments: {
                length: 0
            }
        },
        Scope: [AO, checkscopeContext.AO, globalContext.VO],
        this: undefined
    }複製程式碼

7.f 函式執行,沿著作用域鏈查詢 scope 值,返回 scope 值

8.f 函式執行完畢,f 函式上下文從執行上下文棧中彈出

    ECStack = [
        checkscopeContext,
        globalContext
    ];複製程式碼

9.checkscope 函式執行完畢,checkscope 執行上下文從執行上下文棧中彈出

    ECStack = [
        globalContext
    ];複製程式碼

第二段程式碼就留給大家去嘗試模擬它的執行過程。

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();複製程式碼

不過,在下一篇《JavaScript深入之閉包》中也會提及這段程式碼的執行過程。

下一篇文章

《JavaScript深入之閉包》

相關連結

《JavaScript深入之詞法作用域和動態作用域》

《JavaScript深入之執行上下文棧》

《JavaScript深入之變數物件》

《JavaScript深入之作用域鏈》

《JavaScript深入之從ECMAScript規範解讀this》

重要參考

《一道js面試題引發的思考》

本文寫的太好,給了我很多啟發。感激不盡!

深入系列

JavaScript深入系列目錄地址:github.com/mqyqingfeng…

JavaScript深入系列預計寫十五篇左右,旨在幫大家捋順JavaScript底層知識,重點講解如原型、作用域、執行上下文、變數物件、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎star,對作者也是一種鼓勵。

相關文章