閉包的概念比較抽象,理解起來比較困難,但是仔細(xì)分析一下其實也沒有特別恐怖,面向?qū)ο蟮母拍疃几愣诉@又算啥呢
定義:簡單的說就是定義在函數(shù)內(nèi)部的函數(shù),具有讀取局部變量的能力
從這個概念上可以看出閉包涉及到的js概念有:作用域鏈(局部變量) / 函數(shù)的嵌套(定義在函數(shù)內(nèi)部的函數(shù))/內(nèi)存回收(這個比較難理解)
作用域鏈
作用域鏈?zhǔn)呛瘮?shù)聲明的時候創(chuàng)建的,用于尋找使用到的變量的值的一個索引,他內(nèi)部的規(guī)則是,把自己函數(shù)內(nèi)部的變量放在最前面優(yōu)先讀取,把父級函數(shù)的內(nèi)部變量放在其次,然后依次向上排列,直到全局變量,
如果在子函數(shù)內(nèi)部使用一個變量,如果自己函數(shù)內(nèi)部沒有,則去父級函數(shù)內(nèi)尋找,依次向上,如果函數(shù)內(nèi)部沒有則查看全局變量,全局變量如果也沒有則只為undefine,如果找到了則停止尋找;
var a =0;
function fun1(){
? ?var a=1;
? ?function fun2(){
? ? ? ?var a=2;
? ? ? ?alert(a);
? }
? ?fun2()
}
fun1() //-->2
內(nèi)存回收
函數(shù)運行的時候會為函數(shù)內(nèi)部聲明的變量創(chuàng)建內(nèi)存空間,以備后面的語句調(diào)用,函數(shù)運行結(jié)束后則認(rèn)為這些變量已經(jīng)使用完畢,于是變量被銷毀,再次執(zhí)行的時候則內(nèi)存重新創(chuàng)建;
如果函數(shù)的內(nèi)部又嵌套了另一個函數(shù),而且這個函數(shù)有可能在外部被調(diào)用到,還用到了外部函數(shù)的一些變量上面提到的內(nèi)存回收機(jī)制就會失效,也就是說內(nèi)存不在回收了,
如果外部的函數(shù)調(diào)用后又直接調(diào)用了內(nèi)部函數(shù),如果按之前的內(nèi)存回收的話,內(nèi)部函數(shù)就無法讀區(qū)到外部函數(shù)的變量的值了,這時候js解釋器在遇到這種情況時就會把外部的函數(shù)保存起來以保證自己內(nèi)部的變量不被回收,從而能保證內(nèi)部的函數(shù)能夠正常的調(diào)用,其實這種情況就形成了一個閉包,如果內(nèi)部的函數(shù)被銷毀,或者沒有調(diào)用的可能性后閉包也就被回收了;
var test = [];
var i= 0;
for (;i<3;i=i+1){
? ? test[i]=function(){
? ? ? ? alert(i)
? ? ?}
}
test[0] //3
test[1] //3
test[2] //3
閉包:
var test = [];
var i= 0;
for (;i<3;i=i+1){
? ? test[i]=(function(j){
? ? ? ? ?alert(j)
? ? ?})(i)
}
test[0] //0
test[1] //1
test[2] //2
test實際上就是閉包fun2函數(shù)。它一共運行了兩次,第一次的值是9,第二次的值是10。這證明了,函數(shù)fun1中的局部變量n一直保存在內(nèi)存中,并沒有在fun1調(diào)用后被自動清除,原因就在于fun1是fun2的父函數(shù),而fun2被賦給了一個全局變量,這導(dǎo)致fun2始終在內(nèi)存中,而fun2的存在依賴于fun1,因此fun1也始終在內(nèi)存中,不會在調(diào)用結(jié)束后,被內(nèi)存回收機(jī)制(garbage collection)回收
function fun1(){
? ? var n=9;
? ? ?add=function(){n+=1}//這里的add是一個全局變量,相當(dāng)于fun1函數(shù)的setter方法,也是一個閉包
? ? function fun2(){
? ? ? ? ? alert(n);
? ? ?}
? ? return fun2;
}
var test=fun1();
test(); // 9
add();
test(); // 10