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