原文出處
作用域鏈
當查找變量的時候匹摇,會先從當前上下文的變量對象中查找,如果沒有找到恩沛,就會從父級(詞法層面上的父級)執(zhí)行上下文的變量對象中查找器一,一直找到全局上下文的變量對象宪躯,也就是全局對象卧土。這樣由多個執(zhí)行上下文的變量對象構成的鏈表就叫做作用域鏈惫皱。
下面,讓我們以一個函數(shù)的創(chuàng)建和激活兩個時期來講解作用域鏈是如何創(chuàng)建和變化的尤莺。
函數(shù)創(chuàng)建
函數(shù)有一個內部屬性 [[scope]]旅敷,當函數(shù)創(chuàng)建的時候,就會保存所有父變量對象到其中颤霎,你可以理解 [[scope]] 就是所有父變量對象的層級鏈媳谁,但是注意:[[scope]] 并不代表完整的作用域鏈!
function foo() {
function bar() {
...
}
}
函數(shù)創(chuàng)建時友酱,各自的[[scope]]為:
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
函數(shù)激活(執(zhí)行)
當函數(shù)激活(執(zhí)行)時晴音,進入函數(shù)上下文,創(chuàng)建 VO/AO 后缔杉,就會將活動對象添加到作用鏈的前端锤躁。
這時候執(zhí)行上下文的作用域鏈,我們命名為 Scope:
Scope = [AO].concat([[Scope]]);
至此或详,作用域鏈創(chuàng)建完畢系羞。
捋一捋
以下面的例子為例,結合著之前講的變量對象和執(zhí)行上下文棧霸琴,我們來總結一下函數(shù)執(zhí)行上下文中作用域鏈和變量對象的創(chuàng)建過程:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
執(zhí)行過程如下:
1.checkscope 函數(shù)被創(chuàng)建椒振,保存作用域鏈到 內部屬性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
2.執(zhí)行 checkscope 函數(shù),創(chuàng)建 checkscope 函數(shù)執(zhí)行上下文梧乘,checkscope 函數(shù)執(zhí)行上下文被壓入執(zhí)行上下文棧
ECStack = [
checkscopeContext,
globalContext
];
3.checkscope 函數(shù)并不立刻執(zhí)行澎迎,開始做準備工作,第一步:復制函數(shù)[[scope]]屬性創(chuàng)建作用域鏈
checkscopeContext = {
Scope: checkscope.[[scope]],
}
4.第二步:用 arguments 創(chuàng)建活動對象选调,隨后初始化活動對象夹供,加入形參、函數(shù)聲明仁堪、變量聲明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
}哮洽,
Scope: checkscope.[[scope]],
}
5.第三步:將活動對象壓入 checkscope 作用域鏈頂端
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6.準備工作做完,開始執(zhí)行函數(shù)枝笨,隨著函數(shù)的執(zhí)行袁铐,修改 AO 的屬性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
7.查找到 scope2 的值,返回后函數(shù)執(zhí)行完畢横浑,函數(shù)上下文從執(zhí)行上下文棧中彈出
ECStack = [
globalContext
];