(注1:如果有問(wèn)題歡迎留言探討芹助,一起學(xué)習(xí)洪囤!轉(zhuǎn)載請(qǐng)注明出處跑慕,喜歡可以點(diǎn)個(gè)贊哦M蛎蟆)
(注2:更多內(nèi)容請(qǐng)查看我的目錄。)
1. 簡(jiǎn)介
在JS入門難點(diǎn)解析5-變量對(duì)象中提到相赁,對(duì)于每個(gè)執(zhí)行上下文相寇,都有三個(gè)重要屬性:
- 變量對(duì)象(Variable object,VO)
- 作用域鏈(Scope chain)
- this
這篇文章主要講解作用域鏈钮科。
2. 作用域鏈
來(lái)看《JavaScript高級(jí)程序設(shè)計(jì)》里對(duì)作用域鏈的一段解釋:
當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí)唤衫,會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈(scope chain)。作用域鏈的用途绵脯,是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)佳励。作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對(duì)象蛆挫。如果這個(gè)環(huán)境是函數(shù)赃承,則將其活動(dòng)對(duì)象(active object)作為變量對(duì)象°睬郑活動(dòng)對(duì)象在最開(kāi)始時(shí)只包含一個(gè)變量瞧剖,即arguments對(duì)象(這個(gè)對(duì)象在全局環(huán)境中是不存在的)。作用域鏈中的下一個(gè)變量對(duì)象來(lái)自包含(外部)環(huán)境,而再下一個(gè)變量來(lái)自下一個(gè)包含環(huán)境抓于。這樣做粤,一直延續(xù)到全局執(zhí)行環(huán)境;全局環(huán)境的變量對(duì)象始終都是作用域鏈中的最后一個(gè)對(duì)象捉撮。
標(biāo)識(shí)符解析是沿著作用域鏈一級(jí)一級(jí)地搜索標(biāo)識(shí)符的過(guò)程怕品。搜索過(guò)程始終從作用域鏈的前端開(kāi)始,然后逐級(jí)地向后回溯巾遭,直至找到標(biāo)識(shí)符為止(如果找不到標(biāo)識(shí)符肉康,通常會(huì)導(dǎo)致錯(cuò)誤發(fā)生)。
就是說(shuō)灼舍,作用域鏈吼和,是由當(dāng)前環(huán)境與上層環(huán)境的一系列變量對(duì)象組成,它保證了當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問(wèn)權(quán)限的變量和函數(shù)的有序訪問(wèn)片仿。
3. [[scope]]與函數(shù)創(chuàng)建
函數(shù)的[[scope]]屬性是所有父變量對(duì)象的層級(jí)鏈纹安,在函數(shù)創(chuàng)建時(shí)(函數(shù)生命周期分為函數(shù)創(chuàng)建和函數(shù)調(diào)用階段)存于其中。函數(shù)能訪問(wèn)更高一層上下文的變量對(duì)象,這種機(jī)制是通過(guò)函數(shù)內(nèi)部的[[scope]]屬性來(lái)實(shí)現(xiàn)的砂豌。
注意重要的一點(diǎn)——[[scope]]在函數(shù)創(chuàng)建時(shí)被存儲(chǔ)——靜態(tài)(不變的)厢岂,永遠(yuǎn)永遠(yuǎn),直至函數(shù)銷毀阳距。即:函數(shù)可以永不調(diào)用塔粒,但[[scope]]屬性已經(jīng)寫入,并存儲(chǔ)在函數(shù)對(duì)象中筐摘。由于是靜態(tài)存儲(chǔ)卒茬,再配合上內(nèi)部函數(shù)的[[scope]]屬性是所有父變量的層級(jí)鏈,就導(dǎo)致了閉包的存在咖熟。如下所示:
var a = 10;
function foo() {
alert(a);
}
(function () {
var a = 20;
foo(); // 10,這里會(huì)訪問(wèn)foo中的[[scope]]的VO中的a
})();
這個(gè)例子也清晰的表明圃酵,一個(gè)函數(shù)(這個(gè)例子中為從函數(shù)“foo”返回的匿名函數(shù))的[[scope]]持續(xù)存在,即使是在函數(shù)創(chuàng)建的作用域已經(jīng)完成之后馍管。
這也就是前面我們所說(shuō)郭赐,函數(shù)的作用域在函數(shù)定義的時(shí)候就決定了。這是因?yàn)楹瘮?shù)有一個(gè)內(nèi)部屬性 [[scope]]确沸,當(dāng)函數(shù)創(chuàng)建的時(shí)候捌锭,就會(huì)保存所有父變量對(duì)象到其中,你可以理解 [[scope]] 就是所有父變量對(duì)象的層級(jí)鏈罗捎,但是注意:[[scope]] 并不代表完整的作用域鏈观谦!
舉個(gè)例子:
function foo() {
function bar() {
...
}
}
函數(shù)創(chuàng)建時(shí),各自的[[scope]]為:
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
4. 函數(shù)激活
當(dāng)函數(shù)激活時(shí)桨菜,進(jìn)入函數(shù)上下文豁状,創(chuàng)建 VO/AO 后捉偏,就會(huì)將活動(dòng)對(duì)象添加到作用鏈的前端。
這時(shí)候執(zhí)行上下文的作用域鏈替蔬,我們命名為 Scope:
Scope = [AO].concat([scope]]);
至此告私,作用域鏈創(chuàng)建完畢。
5. 實(shí)例講解
以下面的例子為例承桥,結(jié)合著之前講的變量對(duì)象和執(zhí)行上下文棧,我們來(lái)總結(jié)一下函數(shù)執(zhí)行上下文中作用域鏈和變量對(duì)象的創(chuàng)建過(guò)程:
var scope = 'global scope';
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
執(zhí)行過(guò)程如下:
1.checkscope 函數(shù)被創(chuàng)建根悼,保存作用域鏈到內(nèi)部屬性[[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í)行挤巡,開(kāi)始做準(zhǔn)備工作剩彬,第一步:復(fù)制函數(shù)[[scope]]屬性創(chuàng)建作用域鏈
checkscopeContext = {
Scope: checkscope.[[scope]],
}
4.第二步:用 arguments 創(chuàng)建活動(dòng)對(duì)象,隨后初始化活動(dòng)對(duì)象矿卑,加入形參喉恋、函數(shù)聲明、變量聲明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
}母廷,
Scope: checkscope.[[scope]],
}
5.第三步:將活動(dòng)對(duì)象壓入 checkscope 作用域鏈頂端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6.準(zhǔn)備工作做完轻黑,開(kāi)始執(zhí)行函數(shù),隨著函數(shù)的執(zhí)行琴昆,修改 AO 的屬性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
7.查找到 scope2 的值氓鄙,返回后函數(shù)執(zhí)行完畢,函數(shù)上下文從執(zhí)行上下文棧中彈出
ECStack = [
globalContext
];
參考
JavaScript深入之作用域鏈
前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用域鏈與閉包
JS入門難點(diǎn)解析5-變量對(duì)象
javascript中的[[scope]],scope chain,execution context!
js 中的活動(dòng)對(duì)象 與 變量對(duì)象 什么區(qū)別业舍?
BOOK-《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》