js 闭包及其相关知识点理解
本文結(jié)合個(gè)人學(xué)習(xí)及實(shí)踐,對(duì)閉包及相關(guān)知識(shí)點(diǎn)進(jìn)行總結(jié)記錄,歡迎讀者提出任何不足之處
一、js變量
二、作用域(scope)
三、[[scope]] 和 scope chain
四、作用域(scope)和關(guān)鍵字(this)
五、閉包實(shí)例理解 及 垃圾回收
?
一、js變量
在 ECMAScript 中,變量可以存在兩種類型的值,即原始值和引用值。
原始值:存儲(chǔ)在棧區(qū)(stack)的簡(jiǎn)單數(shù)據(jù)段,變量所在的位置直接存儲(chǔ)變量的值。
原始值包括:Undefined、Null、Boolean、Number 和 String 型
引用值:存儲(chǔ)在堆區(qū)(heap)中的對(duì)象,變量所在的位置存的是地址指針(pointer),指向存儲(chǔ)對(duì)象在內(nèi)存中的地址
?
二、作用域(scope)
1.作用域
作用域指代碼當(dāng)前的上下文環(huán)境,即代碼可以訪問(wèn)和可以被訪問(wèn)的區(qū)域。
?
2.全局作用域(一個(gè)頁(yè)面一般只有一個(gè)全局作用域)
<script>
//這里是全局作用域
var myname1="yaoming";
</script>
?
3.本地作用域/局部作用域
<script>
//這里是全局作用域
var myname1="yaoming";
var myfun=function(){
console.log(myname1); //yaoming
var myname2="liuxiang";
//這里是本地作用域
}
console.log(myname2);
//myname2 is not defined
</script>
myname1打印出yaoming,是因?yàn)榫植坑蚩梢栽L問(wèn)全局域中的變量(反之則不行)
myname2未定義,是因?yàn)槟闳钟虿荒茉L問(wèn)局部域內(nèi)的變量
?
4.函數(shù)域
所有域都只能由函數(shù)域所創(chuàng)建
<script>
// 全局作用域
var?myFunction?=?function?()?{ ??
// 局部作用域1?
var?myOtherFunction?=?function?()?{ ????
//?局部作用域2?
};
};
注:for循環(huán)、while等循環(huán)都不能創(chuàng)建局部作用域
//這里是全局作用域
for(var i=0;i<10;i++){
//code
}
console.log(i); //打印出10
</script>
i 值為10,i 依然屬于全局域,是全局變量,所以循環(huán)不能創(chuàng)建局部作用域
?
5.作用域鏈(scope chain,Scope Chain是一個(gè)鏈表,js中的閉包就是通過(guò)作用域鏈實(shí)現(xiàn)的)
<script>
// 全局作用域
var?myFunction1 =?function?()?{ ??
// 局部作用域1 (scope1)
var?myOtherFunction1 =?function?()?{ ????
//?局部作用域2(scope2)?
};
};
var?myFunction2 =?function?()?{ ??
?
// 局部作用域3? (scope3)
?
var?myOtherFunction2 =?function?()?{ ????
?
//?局部作用域4? (scope4)
?
};
?
};
</script>
圖解:
上圖棧區(qū)中的var a 和 var myFun1 在全局區(qū)域
fun對(duì)應(yīng)myFunction1,在scope1中
fun2對(duì)應(yīng)myOtherFunction1,在scope2中
myOtherFunction1中形成的作用域鏈:scope1--》scope2--》全局作用域
說(shuō)明:a.scope1scope1scope1 中可以訪問(wèn)scope2和全局作用域中的任何變量
b.如果 scope2中使用了 i 變量但是沒(méi)有定義 i 變量,那么它會(huì)往其上級(jí)作用域?qū)ふ?i 變量直到找到為止,找不到為null。
上圖:fun2 中使用了變量 i?
第一步:在scope2 中尋找 i 變量,找到則停止,否則進(jìn)入下一步
第二步:在上一級(jí)域 scope1中繼續(xù)尋找 i 變量,同理找到停止,否則下一步
第三步:在scope1的上級(jí)作用域 全局作用域中 尋找 i 變量 ,找到停止,否則變量未定義
myOtherFunction2中形成的作用域鏈:scope4--》scope3--》全局作用域 (此處同上)
?
?
6.閉包(closure)/詞法作用域/靜態(tài)作用域
當(dāng)B函數(shù)嵌套在A函數(shù)內(nèi),B中引用了A作用域中的變量,
并且B在A的外部調(diào)用了B函數(shù),B函數(shù)是閉包函數(shù)。
<script>
//scope global
function a(){
var myname="liuxiang";
return function b(){
console.log(myname);
//此作用域訪問(wèn)上級(jí)作用域的 myname變量
}
}
var c=a(); //c就是函數(shù)b ,在b的外層函數(shù)a之外被使用,那么此時(shí)就形成了閉包
c(); ? //此處打印出 liuxiang
console.log(this);
</script>
?
閉包函數(shù)會(huì)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式
console.log(this);打印出window對(duì)象,從全局對(duì)象window中找到 變量c并展開如下:
由上圖可以看到,全局變量 c 指向了內(nèi)部函數(shù) b
函數(shù)b的作用域scope=A0+[[scopes]],其中[[scopes]]為函數(shù)b的屬性,此屬性包含了一個(gè)與其形成閉包的函數(shù)a的Closure(a)作用域,和一個(gè)Global全局作用域
Closure(a)中包含所有 b函數(shù)中使用到的變量
Gloal 包含所有的全局變量
?
三、? VO/AO, scope chain , [[scope]]?和 scope
JS 代碼的執(zhí)行
在js代碼執(zhí)行之前,js引擎會(huì)在全局域創(chuàng)建一個(gè) VO (變量對(duì)象) ,在每一個(gè)函數(shù)中的局部域創(chuàng)建一個(gè) AO (活動(dòng)對(duì)象)
VO 指向?qū)儆谌钟虻乃袑?duì)象,進(jìn)入js代碼塊的時(shí)候即被創(chuàng)建
AO 活動(dòng)對(duì)象是進(jìn)入函數(shù)上下文時(shí)被創(chuàng)建的,指向局部作用域的所有對(duì)象
作用域鏈
作用域鏈正是內(nèi)部上下文 所有變量對(duì)象 和 所有父變量對(duì)象 的列表。
仍然以? 二、作用域 中的第 5 點(diǎn) 中的代碼為例
myOtherFunction1 上下文的作用域鏈 為 AO(myOtherFunction1) AO(myFunction1) 和 VO(global)
?
[[scope]]
[[scope]]屬性是在當(dāng)前函數(shù)被定義時(shí)確定,[[scope]]屬性是所有父變量的層級(jí)鏈,位于當(dāng)前的 AO 上下文 之上。
函數(shù)之所有能訪問(wèn) 上級(jí)作用域中的對(duì)象,就是[[scope]]屬性來(lái)實(shí)現(xiàn)的。
?
scope的定義:
scope=AO+[[scope]];? 當(dāng)前的 AO 是作用域 數(shù)組的第一個(gè)對(duì)象,即從當(dāng)前活動(dòng)對(duì)象 往上級(jí)查找 ,則局部變量 比 父級(jí)變量 有更高的優(yōu)先級(jí)。
以 二、作用域 中的第 5 點(diǎn) 中的代碼為例
myOtherFunction1Context.Scope=? myOtherFunction1Context.AO + myOtherFunction1.[[Scope]]
=? myOtherFunction1Context.AO +? myFunction1Context.AO + myFunction1.[[Scope]]
=? myOtherFunction1Context.AO +? myFunction1Context.AO + globalContext.VO
myOtherFunction1的scope數(shù)組是 myOtherFunction1Context.Scope= [ myOtherFunction1Context.AO, myFunction1Context.AO, globalContext.VO ];
?
四、作用域(scope)和關(guān)鍵字(this)
?
五、閉包實(shí)例理解 及 垃圾回收
現(xiàn)有數(shù)組b,b中包含三個(gè)人的姓名和年齡信息?,F(xiàn)在通過(guò)調(diào)用sayHello 方法,分別為每一個(gè)對(duì)象添加一個(gè)說(shuō)出自己名字的方法。
代碼實(shí)現(xiàn)如下:
<script>
var b=[
?? ?{"name":"yaoming",age:40},
?? ?{"name":"liuxiang",age:38},
?? ?{"name":"lining",age:50}
????? ];
function addSayHello(){
?? ?for(var j=0;j<b.length;j++){
?? ??? ?b[j].sayHello=function(){
?? ??? ??? ?return "hello,i am "+b[j].name;
?? ??? ?};
?? ?}
?? ?--j;
}
addSayHello();
console.log(b[0].name+"說(shuō):"+b[0].sayHello());
console.log(b[1].name+"說(shuō):"+b[1].sayHello());
console.log(b[2].name+"說(shuō):"+b[2].sayHello());
?? ?
</script>
控制臺(tái)輸入如下:
發(fā)現(xiàn) 每個(gè)人的sayHello 都會(huì)打印出 我是 lining,這并不是我們想要的結(jié)果。
分析:
addSayHello 執(zhí)行完畢之后,全局作用域 里 b數(shù)組中的每個(gè)成員都有了自己的sayHello方法,
該方法的表達(dá)式:function(){return "hello,i am "+b[j].name;};
當(dāng)調(diào)用b[0].sayHello()時(shí),sayHello.AO中無(wú) j 變量的定義,那么下一步,會(huì)到 addSayHello.AO 中尋找 j 變量,此時(shí)找到了? j 變量,此時(shí) j 變量的值為2
因此 b[0].sayHello()會(huì)返回?"hello,i am "+b[2].name;.同理所有人調(diào)用sayHello方法都會(huì)返回"hello,i am "+b[2].name;
?
正確的方法:
<script>
var b=[
?? ?{"name":"yaoming",age:40},
?? ?{"name":"liuxiang",age:38},
?? ?{"name":"lining",age:50}
????? ];
function addSayHello(){
?? ?for(var j=0;j<b.length;j++){
?? ??? ?b[j].sayHello=(function(index){
?? ??? ??? ?return function(){
?? ??? ??? ??? ?return "hello,i am "+b[index].name;
?? ??? ??? ?}
?? ??? ?})(j);
?? ?}
?? ?--j;
}
addSayHello();
console.log(b[0].name+"說(shuō):"+b[0].sayHello());
console.log(b[1].name+"說(shuō):"+b[1].sayHello());
console.log(b[2].name+"說(shuō):"+b[2].sayHello());
?? ?
</script>
正確的控制臺(tái)運(yùn)行結(jié)果:
思路:錯(cuò)誤的方法中,sayHello 方法中找不到變量j,上級(jí)addSayHello.AO 中只保存其作用域中的變量 j(保存為循環(huán)執(zhí)行后j的最終值),造成所有方法訪問(wèn)同一變量。
如果我們能讓每個(gè) sayHello 方法在 sayHello.AO 中保存自己所需的變量,就解決了剛剛的問(wèn)題。在上面正確的方法中,每次循環(huán)都把當(dāng)前 j 對(duì)應(yīng)的變量以參數(shù)的形式傳給內(nèi)層函數(shù),在內(nèi)層函數(shù)的作用域中保存當(dāng)前的值即可。
總結(jié):
在錯(cuò)誤的方法中,內(nèi)層sayhello() 函數(shù)和 外層函數(shù) addSayHello 形成了閉包,內(nèi)層變量 j 永遠(yuǎn)都指向 外層函數(shù) addSayHello 中的 j變量。
如下圖,每一個(gè)對(duì)象的函數(shù)作用域中 都指向 j=2
改進(jìn)的方法中,sayhello() 函數(shù) 為一個(gè)自執(zhí)行函數(shù)返回的一個(gè)匿名函數(shù)。 sayHello對(duì)應(yīng)的匿名函數(shù) 和 其外層的自執(zhí)行函數(shù) 形成閉包,這里 sayHello對(duì)應(yīng)的匿名函數(shù)中的 index變量 會(huì)指向 其自身的 外層自執(zhí)行函數(shù),其中每個(gè)自執(zhí)行函數(shù)的AO里都分別保存了運(yùn)行時(shí) j 變量的副本 index。
如下圖,每一個(gè)對(duì)象的函數(shù)作用域中 都指向 其自身對(duì)應(yīng)的下標(biāo):index:index
?
JS的垃圾回收機(jī)制:
找到那些不被使用的變量,然后釋放其所占用的內(nèi)存
?
?問(wèn):為什么閉包中用到的變量會(huì)保存在內(nèi)存中?
因?yàn)?全局變量 保存了對(duì)內(nèi)部函數(shù)的引用。所以, 內(nèi)部函數(shù),及其所綁定的上下文環(huán)境均被使用,因此變量不會(huì)被釋放,而是保存在內(nèi)存中。
?
轉(zhuǎn)載于:https://www.cnblogs.com/ahguSH/p/6087666.html
總結(jié)
以上是生活随笔為你收集整理的js 闭包及其相关知识点理解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Hibernate 系列教程9-自关联
- 下一篇: Fragment 生命周期的详情