JavaScript深入之作用域鏈

冴羽發表於2017-04-12

JavaScript深入系列第五篇,講述作用鏈的建立過程,最後結合著變數物件,執行上下文棧,讓我們一起捋一捋函式建立和執行的過程中到底發生了什麼?

前言

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

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

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

今天重點講講作用域鏈。

作用域鏈

《JavaScript深入之變數物件》中講到,當查詢變數的時候,會先從當前上下文的變數物件中查詢,如果沒有找到,就會從父級(詞法層面上的父級)執行上下文的變數物件中查詢,一直找到全域性上下文的變數物件,也就是全域性物件。這樣由多個執行上下文的變數物件構成的連結串列就叫做作用域鏈。

下面,讓我們以一個函式的建立和啟用兩個時期來講解作用域鏈是如何建立和變化的。

函式建立

《JavaScript深入之詞法作用域和動態作用域》中講到,函式的作用域在函式定義的時候就決定了。

這是因為函式有一個內部屬性 [[scope]],當函式建立的時候,就會儲存所有父變數物件到其中,你可以理解 [[scope]] 就是所有父變數物件的層級鏈,但是注意:[[scope]] 並不代表完整的作用域鏈!

舉個例子:


function foo() {
    function bar() {
        ...
    }
}複製程式碼

函式建立時,各自的[[scope]]為:


foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];複製程式碼

函式啟用

當函式啟用時,進入函式上下文,建立 VO/AO 後,就會將活動物件新增到作用鏈的前端。

這時候執行上下文的作用域鏈,我們命名為 Scope:


Scope = [AO].concat([[Scope]]);複製程式碼

至此,作用域鏈建立完畢。

捋一捋

以下面的例子為例,結合著之前講的變數物件和執行上下文棧,我們來總結一下函式執行上下文中作用域鏈和變數物件的建立過程:

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

執行過程如下:

1.checkscope 函式被建立,儲存作用域鏈到 內部屬性[[scope]]

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

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

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

3.checkscope 函式並不立刻執行,開始做準備工作,第一步:複製函式[[scope]]屬性建立作用域鏈

checkscopeContext = {
    Scope: checkscope.[[scope]],
}複製程式碼

4.第二步:用 arguments 建立活動物件,隨後初始化活動物件,加入形參、函式宣告、變數宣告

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    }
}複製程式碼

5.第三步:將活動物件壓入 checkscope 作用域鏈頂端

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}複製程式碼

6.準備工作做完,開始執行函式,隨著函式的執行,修改 AO 的屬性值

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, [[Scope]]]
}複製程式碼

7.查詢到 scope2 的值,返回後函式執行完畢,函式上下文從執行上下文棧中彈出

ECStack = [
    globalContext
];複製程式碼

下一篇文章

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

本文相關連結

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

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

《JavaScript深入之變數物件》

深入系列

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

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

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

相關文章