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