我們先來(lái)了解幾個(gè)概念:
執(zhí)行期上下文:當(dāng)函數(shù)執(zhí)行時(shí)罪帖,會(huì)創(chuàng)建一個(gè)稱為執(zhí)行期上下文的內(nèi)部對(duì)象热监。一個(gè)執(zhí)行期上下文定義了一個(gè)函數(shù)執(zhí)行時(shí)的環(huán)境嚎幸,函數(shù)每次執(zhí)行時(shí)對(duì)應(yīng)的執(zhí)行上下文都是獨(dú)一無(wú)二的揖铜,所以多次調(diào)用一個(gè)函數(shù)會(huì)導(dǎo)致創(chuàng)建多個(gè)執(zhí)行上下文弹灭,當(dāng)函數(shù)執(zhí)行完畢州疾,它所產(chǎn)生的執(zhí)行上下文被銷毀辜限。
<pre>解析1:執(zhí)行期上下文指的就是函數(shù)執(zhí)行前一刻所產(chǎn)生的AO對(duì)象</pre>
<pre>解析2:函數(shù)執(zhí)行環(huán)境就是指變量提升函數(shù)提升得到的那些AO對(duì)象屬性</pre>
<pre>解析3:
function test() { }
函數(shù)多次調(diào)用,產(chǎn)生不同的AO對(duì)象:
test(); ---->AO{}
test(); ---->AO{}
函數(shù)執(zhí)行完畢之后對(duì)應(yīng)的AO對(duì)象銷毀严蓖。
</pre>
[[scope]]:每個(gè)js函數(shù)都是一個(gè)對(duì)象薄嫡,對(duì)象中有些屬性我們可以訪問(wèn),但有些不可以颗胡,這些屬性僅供js引擎存取毫深,[[scope]]就是其中一個(gè)。
function test() { }
我們可以訪問(wèn)的函數(shù)屬性(如:test.length/test.prototype);
我們不能訪問(wèn)但著實(shí)存在的函數(shù)屬性(如:test.[[scope]])
作用域鏈:[[scope]]中所存儲(chǔ)的執(zhí)行期上下文對(duì)象的集合毒姨,這個(gè)集合呈鏈?zhǔn)竭B接哑蔫,我們把這種鏈?zhǔn)竭B接叫做作用域鏈。
結(jié)合著例子來(lái)理解一下:
function a() {
function b() {
var b=234;
}
}
var glob = 100;
a();
a函數(shù)被定義時(shí)發(fā)生如下過(guò)程:
先不要細(xì)琢磨上面GO里放置的各個(gè)屬性弧呐。上面a.[[scope]]還沒(méi)有構(gòu)成一個(gè)鏈闸迷,只有GO對(duì)象的存在,下面繼續(xù)俘枫。
a函數(shù)被執(zhí)行時(shí)腥沽,發(fā)生如下過(guò)程:
a函數(shù)執(zhí)行前一刻所產(chǎn)生的AO對(duì)象放到了a函數(shù)作用域a.[[scope]]的頂端;現(xiàn)在a.[[scope]]上已構(gòu)成一個(gè)鏈.
查找變量:從作用域鏈的頂端依次向下查找(再補(bǔ)充:在哪個(gè)函數(shù)里查找變量鸠蚪,就去哪個(gè)函數(shù)的作用域頂端去查找)今阳,最標(biāo)準(zhǔn)的說(shuō)法师溅。
再繼續(xù)研究剛剛的例子:
function a() {
function b() {
var b=234;
}
}
var glob = 100;
a();
b函數(shù)被創(chuàng)建時(shí),發(fā)生如下過(guò)程:
也就是說(shuō)b函數(shù)剛剛出生時(shí)所在的環(huán)境是a執(zhí)行的結(jié)果盾舌,直接給b函數(shù)的出生創(chuàng)造好了環(huán)境墓臭。
b函數(shù)被執(zhí)行時(shí),發(fā)生如下過(guò)程:
b函數(shù)執(zhí)行前一刻產(chǎn)生的AO對(duì)象放置在b.[[scope]]的最頂端⊙矗現(xiàn)在透徹地理解一下窿锉,在函數(shù)b中去訪問(wèn)變量時(shí),是在b函數(shù)的作用域[[scope]]最頂端去查找變量窖维。
再深入剖析看看榆综,檢查自己是否理解清楚了:
首先,a作用域中的頂端AO與b作用域的第二個(gè)AO是同一個(gè)AO的引用么铸史?還是兩個(gè)不同的AO?
答案:是同一個(gè)AO的引用怯伊,下面代碼來(lái)驗(yàn)證一下
function a() {
function b() {
var b=234;
aa=0;
}
var aa=123;
b( );
console.log(aa);//輸出0琳轿,代表變量aa在語(yǔ)句b()執(zhí)行完之后,值被改變了耿芹。
}
var glob = 100;
a();
GO{
glob:100,
a:~~~~~
}
AO(a){
aa:0,
b:~~~~~~
}
再來(lái),在上面的例子中,第7行b函數(shù)執(zhí)行完之后移怯,概念上是執(zhí)行期上下文被銷毀邦泄,而實(shí)際函數(shù)a和函數(shù)b的作用域變化應(yīng)該是什么樣的呢?
答案:
- b函數(shù)執(zhí)行完之后砸彬,自己的執(zhí)行期上下文AO被干掉(銷毀)颠毙,即b.[[scope]]回到b被定義的狀態(tài)。
- 往下進(jìn)行到第9行砂碉,a函數(shù)執(zhí)行完后蛀蜜,b函數(shù)作用域b.[[scope]]直接被銷毀;同時(shí)增蹭,a函數(shù)的執(zhí)行期上下文AO被銷毀滴某,a.[[scope]]回到被定義的狀態(tài),a函數(shù)等待下一次被調(diào)用執(zhí)行滋迈。
思考一下霎奢,下面函數(shù)執(zhí)行的作用域鏈:
function a() {
function b(){
function c() {
}
c();
}
b();
}
a();
具體過(guò)程如下
a 定義: a.[[scope]] --> 0:GO
a 執(zhí)行: a.[[scope]] --> 0:AO(a)
1:GO
b 定義: b.[[scope]] --> 0:AO(a)
1:GO
注意b執(zhí)行了才會(huì)產(chǎn)生c的定義哈!饼灿!
b 執(zhí)行: b.[[scope]] --> 0:AO(b)
1:AO(a)
2:GO
c 定義: c.[[scope]] --> 0:AO(b)
1:AO(a)
2:GO
c 執(zhí)行: c.[[scope]] --> 0:AO(c)
1:AO(b)
2:AO(a)
3:GO
現(xiàn)在再來(lái)看這句話幕侠,函數(shù)里邊能訪問(wèn)函數(shù)外邊的變量,但函數(shù)外邊不能訪問(wèn)呢函數(shù)里邊的變量赔退;從上邊的過(guò)程來(lái)看橙依,在b中訪問(wèn)c中的局部變量证舟,是不可能的,因?yàn)閎.[[scope]]中不存在函數(shù)c的執(zhí)行期上下文AO(c)窗骑。