js深入基礎--作用域鏈005
前言
在js深入基礎--執行上下文棧003中講到,當JavaScript程式碼執行一段可執行程式碼(executable code)時,會建立對應的執行上下文(execution context)。
對於每個執行上下文,都有三個重要屬性:
- 變數物件(Variable object,VO)
- 作用域鏈(Scope chain)
- this
今天重點講講作用域鏈。
作用域鏈
在js深入基礎--變數物件004中講到,當查詢變數的時候,會先從當前上下文的變數物件中查詢,如果沒有找到,就會從父級(詞法層面上的父級)執行上下文的變數物件中查詢,一直找到全域性上下文的變數物件,也就是全域性物件。這樣由多個執行上下文的變數物件構成的連結串列就叫做作用域鏈。
下面,讓我們以一個函式的建立和啟用兩個時期來講解作用域鏈是如何建立和變化的。
函式建立
在js深入基礎--作用域002 中講到,函式的作用域在函式定義的時候就決定了。
這是因為函式有一個內部屬性[[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
},
Scope: checkscope.[[scope]],
}
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
];