JavaScript作用域和閉包

2021-9-14    前端達(dá)人


前言

深入了解閉包和作用域鏈就需先了解函數(shù)預(yù)編譯的過(guò)程


一、預(yù)編譯

JavaScript:運(yùn)行三部曲:
語(yǔ)法分析–預(yù)編譯–解釋執(zhí)行
預(yù)編譯:
發(fā)生在函數(shù)執(zhí)行的前一刻。
函數(shù)聲明整體提升,變量只聲明提升。
1.函數(shù)預(yù)編譯的過(guò)程:
1.創(chuàng)建AO對(duì)象Activation Object(執(zhí)行期上下文,其作用就是我們理解的作用域,函數(shù)產(chǎn)生的執(zhí)行空間庫(kù))
2.找形參和變量聲明,將變量和形參名作為AO屬性名,值為undefined
3.將實(shí)參值與形參統(tǒng)一
4.找到函數(shù)聲明,將函數(shù)名作為屬性名,值為函數(shù)體。
例:

function test (a, b){ console.log(a); c = 0; var c; a = 3; b = 2; console.log(b); function b (){}; function d (){}; console.log(b); } test(1); /*答案:1,2,2
答題過(guò)程:找形參和變量聲明,將變量和形參名作為 AO 屬性名,值為 undefined, AO{
 a : 1,
 b : undefined,
 c : undefined
}
函數(shù)聲明 function b(){}和 function d(){},AO{
 a : 1,
 b : function b(){},
 c : undefined,
 d : function d(){}
}
執(zhí)行 console.log(a);答案是 1
執(zhí)行 c = 0;變 AO{
 a : 1,
 b : function b(){},
 c : 0,
 d : function d(){}
}
var c 不用管,因?yàn)?c 已經(jīng)在 AO 里面了
執(zhí)行 a = 3;改 AO{
 a : 3,
 b : function b(){},
 c : 0,
 d : function d(){}
}
執(zhí)行 b = 2;改 AO{
 a : 3,
 b : 2,
 c : 0,
 d : function d(){}
}
執(zhí)行 console.log(b);答案是 2
function b () {}和 function d(){}已經(jīng)提過(guò)了,不用管
執(zhí)行 console.log(b);答案是 2*/ 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

2.全局預(yù)編譯它和函數(shù)預(yù)編譯步驟一樣,但它創(chuàng)造的是GO(全局對(duì)象):
1.生成了一個(gè) GO 的對(duì)象 Global Object(window 就是 GO
2.找變量聲明…
3.找函數(shù)聲明…

任何全局變量都是 window 上的屬性
變量沒(méi)有聲明就賦值了,歸 window 所有,就是在 GO 里面預(yù)編譯。
例 :

function test(){ var a = b =123; console.log(window.b); } test(); 答案 a 是 undefined,b 是 123 先生成 GO{ b : 123 } 再有 AO{ a : undefined } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

想執(zhí)行全局,先生成 GO,在執(zhí)行 test 的前一刻生成 AO
函數(shù)里找變量,因?yàn)镚O和AO有幾層嵌套關(guān)系,近的優(yōu)先,從近的到遠(yuǎn)的, AO里有就看 AO,AO 沒(méi)有才看 GO。所以函數(shù)局部變量和全局變量同名,函數(shù)內(nèi)只會(huì)用局部。

二、作用域精講

作用域定義:變量(變量作用于又稱上下文)和函數(shù)生效(能被訪問(wèn))的區(qū)域
全局、局部變量
作用域的訪問(wèn)順序:函數(shù)外面不能用函數(shù)里面的。里面的可以訪問(wèn)外面的,外面的不能訪問(wèn)里面的,彼此獨(dú)立的區(qū)間不能相互訪問(wèn)。

1.[[scope]]: 每個(gè) javascript 函數(shù)都是一個(gè)對(duì)象,對(duì)象中有些屬性我們可以訪問(wèn),但有些不可以,這些屬性僅供 javascript 引擎存取,[[scope]]就是其中一個(gè)。[[scope]]指的就是我們所說(shuō)的作用域,其中存儲(chǔ)了運(yùn)行期上下文的集合。

2.執(zhí)行期上下文: 當(dāng)函數(shù)在執(zhí)行的前一刻,會(huì)創(chuàng)建一個(gè)稱為執(zhí)行期上下文的內(nèi)部對(duì)象(AO)。
一個(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í)行完畢,執(zhí)行上下文被銷毀。

3.作用域鏈:[[scope]]中所存儲(chǔ)的執(zhí)行期上下文對(duì)象的集合(GO和AO),這個(gè)集合呈鏈?zhǔn)芥溄?,我們把這種鏈?zhǔn)芥溄咏凶鲎饔糜蜴湣?

4.查找變量: 在哪個(gè)函數(shù)里面查找變量,就從哪個(gè)函數(shù)作用域鏈的頂端依次向下查找(先查自己的AO,再查父級(jí)的AO,一直到最后的GO)。
函數(shù)類對(duì)象,我們能訪問(wèn) test.name
test.[[scope]]隱式屬性——作用域

作用域鏈圖解:

function a (){ function b (){ var bb = 234; aa = 0; } var aa = 123; b(); console.log(aa) } var glob = 100; a(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

0 是最頂端,1 是次頂端,查找順序是從最頂端往下查
在這里插入圖片描述
在全局預(yù)編譯中函數(shù)a定義時(shí),它的[[scope]]屬性中有GO對(duì)象。
在這里插入圖片描述
在函數(shù)a執(zhí)行前先函數(shù)預(yù)編譯,創(chuàng)建自己的AO對(duì)象,并存儲(chǔ)在[[scope]]屬性上,與之前存儲(chǔ)的GO成鏈?zhǔn)?。同時(shí)函數(shù)b被創(chuàng)建定義。
在這里插入圖片描述
在b被創(chuàng)建時(shí),它生成的[[scope]]屬性直接存儲(chǔ)了父級(jí)的[[scope]],它有了父級(jí)的AO和GO。
在這里插入圖片描述
b函數(shù)執(zhí)行前預(yù)編譯,生成自己的AO,存儲(chǔ)在[[scope]]屬性中。

詳解過(guò)程: 注意[[scope]]它是數(shù)組,存儲(chǔ)的都是引用值。
b 中 a 的 AO 與 a 的 AO,就是同一個(gè) AO,b 只是引用了 a 的 AO,GO 也都是同一個(gè)。
function b(){}執(zhí)行完,干掉的是 b 自己的 AO(銷毀執(zhí)行期上下文)(去掉連接線),下次 function b 被執(zhí)行時(shí),產(chǎn)生的是新的 b 的 AO。b 執(zhí)行完只會(huì)銷毀自己的 AO,不會(huì)銷毀 a 的 AO。會(huì)退回到b被定義時(shí)(仍有父級(jí)的AO和GO)。
function a(){}執(zhí)行完,會(huì)把 a 自己的 AO 銷毀【也會(huì)把 function b的[[scope]]也銷毀】,只剩 GO(回歸到 a 被定義的時(shí)候),等下次 function a再次被執(zhí)行時(shí),會(huì)產(chǎn)生一個(gè)全新的 AO,里面有一個(gè)新的 b 函數(shù)。。。。。。周而復(fù)始。

思考一個(gè)問(wèn)題:如果 function a 不被執(zhí)行,下面的 function b 和 function c 都是看不到的(也不會(huì)被執(zhí)行,被折疊)。只有 function a 被執(zhí)行,才能執(zhí)行 function a 里面的內(nèi)容a();不執(zhí)行,根本看不到 function a (){}里面的內(nèi)容,但我們想在a函數(shù)外面調(diào)用b函數(shù)怎么辦呢,于是閉包出現(xiàn)了。

三、閉包

閉包的定義

當(dāng)內(nèi)部函數(shù)被保存到外部時(shí),將會(huì)生成閉包。但凡是內(nèi)部的函數(shù)被保存到外部,一定生成閉包。
閉包的問(wèn)題:閉包會(huì)導(dǎo)致原有作用域鏈不釋放,作用域中的局部變量一直被使用著,導(dǎo)致該作用域釋放不掉,造成內(nèi)存泄露(就是占有過(guò)多內(nèi)存,導(dǎo)致內(nèi)存越來(lái)越少,就像泄露了一樣)
例:

function a(){ function b(){ var b=456; console.log(a); console.log(b); } var a=123; return b; } var glob = a(); glob(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

答案 123,456。
function a(){ }是在 return b 之后才執(zhí)行完,才銷毀。而return b 把 b(包括 a 的 AO)保存到外部了(放在全局)當(dāng) a 執(zhí)行完砍掉自己的 AO 時(shí)(砍掉對(duì)AO存儲(chǔ)地址的指針),因?yàn)閎還保存著對(duì)a的AO的引用,所以內(nèi)存清除機(jī)制不會(huì)清除掉a的AO, b 依然可以訪問(wèn)到 a 的 AO。

閉包的作用:

1.實(shí)現(xiàn)共有變量

function test(){ var num=100; function a(){ num++; } function b(){ num--; } return [a,b]; } var myArr=test(); myArr[0](); myArr[1](); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

答案 101 和 100。
思考過(guò)程:說(shuō)明兩個(gè)用的是一個(gè) AO。
myArr[0]是數(shù)組第一位的意思,即 a,myArr0;就是執(zhí)行函數(shù) a 的意思;
myArr[1]是數(shù)組第二位的意思,即 b,myArr1; 就是執(zhí)行函數(shù) b 的意思。
test doing test[[scope]] 0:testAO
1:GO
a defined a.[[scope]] 0 : testAO
1 : GO
b defined b.[[scope]] 0 : testAO
1 : GO
return[a, b]將 a 和 b 同時(shí)被定義的狀態(tài)被保存出來(lái)了
當(dāng)執(zhí)行 myArr0;時(shí)
a doing a.[[scope]] 0 : aAO
1 : testAO
2 : GO
當(dāng)執(zhí)行 myArr1;時(shí)
b doing b.[[scope]] 0 : bAO
1 : a 運(yùn)行后的 testAO
2 : GO
a 運(yùn)行后的 testAO, 與 a doing 里面的 testAO 一模一樣
a 和 b 連線的都是 test 環(huán)境,對(duì)應(yīng)的一個(gè)閉包

2.可以做緩存(存儲(chǔ)結(jié)構(gòu))

function eater(){ var food=""; var obj={ eat : function (myFood){ console.log("i am eating"+food); food =""; }, push : function (myFood){ food = myFood; } } return obj; } var eater1 = eater(); eater1.push("banana"); eater1.eat(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

答案 i am eating banana,eat 和 push 操作的是同一個(gè) food
在 function eater(){里面的 food}就相當(dāng)于一個(gè)隱式存儲(chǔ)的機(jī)構(gòu)
obj 對(duì)象里面是可以有 function 方法的,也可以有屬性,方法就是函數(shù)的表現(xiàn)形式

3.可以實(shí)現(xiàn)封裝,屬性私有化
只能調(diào)用函數(shù)方法,不能修改函數(shù)的屬性。

4.模塊化開(kāi)發(fā),防止污染全局變量


























藍(lán)藍(lán)設(shè)計(jì)建立了UI設(shè)計(jì)分享群,每天會(huì)分享國(guó)內(nèi)外的一些優(yōu)秀設(shè)計(jì),如果有興趣的話,可以進(jìn)入一起成長(zhǎng)學(xué)習(xí),請(qǐng)掃碼藍(lán)小助,報(bào)下信息,藍(lán)小助會(huì)請(qǐng)您入群。歡迎您加入噢~~希望得到建議咨詢、商務(wù)合作,也請(qǐng)與我們聯(lián)系。

分享此文一切功德,皆悉回向給文章原作者及眾讀者.

轉(zhuǎn)自:csdn
免責(zé)聲明:藍(lán)藍(lán)設(shè)計(jì)尊重原作者,文章的版權(quán)歸原作者。如涉及版權(quán)問(wèn)題,請(qǐng)及時(shí)與我們?nèi)〉寐?lián)系,我們立即更正或刪除。

藍(lán)藍(lán)設(shè)計(jì)www.bouu.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 平面設(shè)計(jì)服務(wù)

分享本文至:

日歷

鏈接

個(gè)人資料

存檔