"官方"的解釋是指一個(gè)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù)),因而這些變量也是該表達(dá)式的一部分;
紅皮書是這樣說的,閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中變量的函數(shù);常見的創(chuàng)建閉包的方式就是在一個(gè)函數(shù)中再創(chuàng)建一個(gè)函數(shù);
閉包是一種特殊的對象,
淺談js之閉包
。它由兩部分構(gòu)成:函數(shù),以及創(chuàng)建該函數(shù)的環(huán)境。環(huán)境由閉包創(chuàng)建時(shí)在作用域中的任何局部變量組成;光看定義是云里霧里,但是到了真正的代碼了又是什么樣的形式呢?經(jīng)典的閉包例子:
function fn() {
var name = 4;
return function () {
var n = 0;
alert(++n);
alert(++name);
}
}
var fun = fn();
fun();//n =>1,name=>5;
fun();// n =>1,name=>6;
這里就有閉包產(chǎn)生了,fun就是閉包;這個(gè)閉包由fn中的匿名函數(shù)和name變量構(gòu)成。,但是呢,它又是一種特殊的函數(shù),它是一種能能夠讀取其他函數(shù)內(nèi)部變量的特殊函數(shù);fn中的匿名函數(shù)的父函數(shù)在執(zhí)行完了之后,按正常來說,它應(yīng)該被銷毀,里面的變量也被銷毀,,但是里面的變量現(xiàn)在沒有被銷毀,而是被這個(gè)匿名函數(shù)引用著;(說實(shí)在的它不應(yīng)該被銷毀,因?yàn)檫@個(gè)匿名函數(shù)還沒有執(zhí)行,還要用上一級的函數(shù)的中的變量,你給我銷毀了,我可怎么辦,那不是讓我報(bào)錯(cuò)呀,但是呢,我給你用,不銷毀,這又不符合規(guī)矩,按規(guī)矩是這樣的:當(dāng)一個(gè)函數(shù)執(zhí)行完之后,是要被立即銷毀的,執(zhí)行環(huán)境銷毀,里面的變量銷毀,你現(xiàn)在不讓我銷毀,那不亂套了,那怎么辦呢,于是乎,一群磚家,就說趕這種方式叫閉包吧)意思是說我跟你們不一樣;因?yàn)槭翘厥夂瘮?shù),代碼的最后一句fun()執(zhí)行完之后,name變量還是沒有釋放,但是每次執(zhí)行fun,里面的n變量是都是新創(chuàng)建的,執(zhí)行完之后又釋放掉;要是你明白了就不需要看括號里的內(nèi)容了(這里我就形象的說為什么說name變量會一直在內(nèi)存中?在剛開始的時(shí)候,父函數(shù)fn在剛要執(zhí)行完了,開始銷毀時(shí),匿名子函數(shù)就說了,我要用你的name變量,你先別銷毀了,父函數(shù)說好吧,于是乎,父函數(shù)執(zhí)行完之后(歸天了),就沒有銷毀name,在當(dāng)你調(diào)用fun,執(zhí)行匿名子函數(shù),fun()調(diào)用完了,你把自己家的n變量銷毀了,fun就說了name又不是我的東西,我就是用了一下,憑什么我給銷毀,我不給銷毀,但是這時(shí)父函數(shù)已經(jīng)去世了(執(zhí)行完了),于是就產(chǎn)生了內(nèi)存消耗,除非你手動(dòng)銷毀,垃圾收回機(jī)制不會自動(dòng)收回;這又牽扯到內(nèi)存泄漏,性能問題了,后面說。)
作用域鏈:
講到這里,如果要想整整的明白還有知道作用域鏈,和垃圾收回機(jī)制;
我就說說上面代碼執(zhí)行時(shí)的作用域鏈:
我就說說這張圖是什么意思,這種圖是執(zhí)行var fun = fu();fun();這兩句代碼時(shí)所發(fā)生的情況;
其實(shí)匿名函數(shù)在fu()被返回時(shí),它的作用域鏈就被初始化為包含全局變量對象和fu函數(shù)的活動(dòng)對象;也就是說當(dāng)fu函數(shù)執(zhí)行完返回后,它的執(zhí)行環(huán)境會被銷毀,但是其活動(dòng)對象不會被銷毀,仍然在內(nèi)存中,因?yàn)槟涿瘮?shù)的作用域鏈中引用了這個(gè)活動(dòng)對象。只有到匿名函數(shù)被手動(dòng)銷毀時(shí)才銷毀;其實(shí)在fu執(zhí)行完后,紅字顯示的部分就消失了,就活動(dòng)變量沒有消失;
再說一點(diǎn)關(guān)于作用域鏈的問題:
1。作用域鏈中的變量對象(函數(shù)中叫活動(dòng)對象)保存的是變量和函數(shù);
2.作用域鏈的作用就是為了保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問。
3.查找一個(gè)變量是從作用域鏈的最前端,逐漸向上找,只要找到就不再向上找了,不論上面是否還有這個(gè)值;
4.就以上面的fu函數(shù)為例吧,在聲明fu函數(shù)的時(shí)候就開始預(yù)先創(chuàng)建一個(gè)包含全局變量對象的作用域鏈了(如果嵌套多了,其實(shí)就是在函數(shù)聲明的地方創(chuàng)建父函數(shù)及其之上的作用域鏈),這個(gè)作用域鏈將被保存在剛創(chuàng)建函數(shù)的內(nèi)部[[Scope]]的屬性中;當(dāng)調(diào)用fu函數(shù)時(shí),會為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境,然后通過復(fù)制函數(shù)的[[Scope]]中的作用域鏈構(gòu)建起執(zhí)行環(huán)境的作用域鏈;之后還要?jiǎng)?chuàng)建一個(gè)本函數(shù)的活動(dòng)對象,并把這個(gè)活動(dòng)對象推入執(zhí)行環(huán)境作用域鏈的前端。
5.作用域鏈本質(zhì)上就是一個(gè)指向變量對象的指針列表。
垃圾回收機(jī)制:
再說說垃圾回收機(jī)制:
1.如果一個(gè)對象不再有引用了,這個(gè)對象就會被GC收回;
2,如果兩個(gè)對象互相引用,但不被第三個(gè)引用,這兩個(gè)互相引用的對象也會收回的。
而閉包不再這個(gè)范疇之內(nèi)。
閉包的特性:
1.引用的變量不被垃圾回收機(jī)制收回
2.函數(shù)內(nèi)部可以引用外部的變量;
3.函數(shù)里面嵌套函數(shù)
閉包的用處(好處):
1.私有變量和方法
var a=(function() {
var privateNum = 1;
function privateFun(val) {
alert(val);
}
return {
publicFun: function() {
privateFun(2);
},
publicNum:function() {
return privateNum;
}
}
})();
a.publicNum();//1
a.publicFun();//2
如果你用a.privateNum,a.privateFun();這是會報(bào)錯(cuò)的。
2.實(shí)現(xiàn)一些變量的累加
function a() {
var n=0;
return function () {
n++;
alert(n);
}
}
var b = a();
b();//1
b();//2
b();//3
這里只是要使用累加,就這樣干,具體還要具體分析,原理是這樣了
因閉包產(chǎn)生的問題
初學(xué)者常見的,循環(huán)閉包
大部分我們所寫的 Web JavaScript. 代碼都是事件驅(qū)動(dòng)的 — 定義某種行為,然后將其添加到用戶觸發(fā)的事件之上(比如點(diǎn)擊或者按鍵),
電腦資料
《淺談js之閉包》(http://m.stanzs.com)。我們的代碼通常添加為回調(diào):響應(yīng)事件而執(zhí)行的函數(shù)。閉包循環(huán)問題
你不管點(diǎn)擊哪一個(gè),都alert”我是5號“;
原因就是你循環(huán)了五次產(chǎn)生了五個(gè)閉包,而這5個(gè)閉包共享一個(gè)變量i,說的明白一點(diǎn)就是,在for循環(huán)結(jié)束時(shí),只是把這五個(gè)匿名函數(shù)注冊給click事件,當(dāng)時(shí)在循環(huán)的時(shí)候并沒有執(zhí)行,當(dāng)循環(huán)結(jié)束了,此時(shí)i的值是5;之后你去點(diǎn)擊p標(biāo)簽,你點(diǎn)擊哪一個(gè)就執(zhí)行哪一個(gè)對應(yīng)的匿名函數(shù)(這個(gè)時(shí)候才執(zhí)行),這時(shí)候匿名中發(fā)現(xiàn)一個(gè)i,匿名中沒有定義i,于是沿著作用域鏈找,找到了,但是這時(shí)候循環(huán)早就結(jié)束了,i等于5,于是彈出”我是5號“來;點(diǎn)擊其他的同理;
怎么解決呢:
一種方法是再創(chuàng)建一個(gè)閉包,把js代碼改為這樣就行了
var page = document.getElementsByTagName("p");
for(var i=0; i !function(num) { page[i].onclick = function () { alert("我是"+num+"號"); } }(i) } 我只說一點(diǎn),這次五個(gè)閉包不共享num,而是創(chuàng)建五個(gè)num變量 還有一種解決方式: var page = document.getElementsByTagName("p"); for(var i=0; i page[i].num = i//先把每個(gè)變量值存起來 page[i].onclick = function () { alert("我是"+this.num+"號"); } } 閉包中的this對象 var num = 1; var bj = { num:2, getNum:function() { return function () { return this.num; } } } alert(obj.getNum()());//num -> 1 為什么不彈出2呢,這里是說明閉包中你需要注意現(xiàn)在的this的指向那一個(gè)對象,其實(shí)記住一句話就永遠(yuǎn)不會用錯(cuò)this的指向問題,this永遠(yuǎn)指向調(diào)用它的作用域; 如果這樣寫你就可能理解了 var num = 1; var bj = { num:2, getNum:function() { return function () { return this.num; } } } var a = obj.getNum(); alert(window.a());//1 其實(shí)是window對象調(diào)用的,這就是說閉包中的this讓你看不清this的指向; 要是讓它alert 2你要這樣: var num = 1; var bj = { num:2, getNum:function() { var _this = this;//在這里保存this return function () { return _this.num; } } } var a = obj.getNum(); alert(window.a()); 性能考量 如果不是因?yàn)槟承┨厥馊蝿?wù)而需要閉包,在沒有必要的情況下,在其它函數(shù)中創(chuàng)建函數(shù)是不明智的,因?yàn)殚]包對腳本性能具有負(fù)面影響,包括處理速度和內(nèi)存消耗。 例如,在創(chuàng)建新的對象或者類時(shí),方法通常應(yīng)該關(guān)聯(lián)于對象的原型,而不是定義到對象的構(gòu)造器中。原因是這將導(dǎo)致每次構(gòu)造器被調(diào)用,方法都會被重新賦值一次(也就是說,為每一個(gè)對象的創(chuàng)建)。 function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; } 應(yīng)該是這樣 function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; }; 示例中,繼承的原型可以為所有對象共享,且不必在每一次創(chuàng)建對象時(shí)定義方法