相信眾多JS的lovers都聽說(shuō)過(guò)這句話:閉包很重要但是很難理解。
我起初也是這么覺(jué)得向挖,但是當(dāng)我努力學(xué)習(xí)了JS的一些深層的原理以后我倒覺(jué)得閉包并不是那么不好理解月帝,反倒是讓我感覺(jué)出一種很美的感覺(jué)先馆。當(dāng)我徹底理解閉包的那一剎那阱扬,心中油然產(chǎn)生一種十分愉悅感覺(jué)泣懊,就像**"酒酣尚醉,花未全開"**那種美景一樣麻惶。
撥開閉包神秘的面紗
我們先看一個(gè)閉包的例子:
function foo() {
? ? let a = 2;
? ? function bar() {
? ? ? ? console.log( a );
? ? }
? ? return bar;
}
let baz = foo();
baz();
大家肯定都寫過(guò)類似的代碼馍刮,相信很多小伙伴也知道這段代碼應(yīng)用了閉包,but, Why does the closure be generated and Where is closure?
來(lái)窃蹋,我們慢慢分析:
首先必須先知道閉包是什么卡啰,才能分析出閉包為什么產(chǎn)生和閉包到底在哪?
當(dāng)一個(gè)函數(shù)能夠記住并訪問(wèn)到其所在的詞法作用域及作用域鏈警没,特別強(qiáng)調(diào)是在其定義的作用域外進(jìn)行的訪問(wèn)匈辱,此時(shí)該函數(shù)和其上層執(zhí)行上下文共同構(gòu)成閉包。
需要明確的幾點(diǎn):
閉包一定是函數(shù)對(duì)象(wintercn大大的閉包考證)
閉包和詞法作用域杀迹,作用域鏈亡脸,垃圾回收機(jī)制息息相關(guān)
當(dāng)函數(shù)一定是在其定義的作用域外進(jìn)行的訪問(wèn)時(shí),才產(chǎn)生閉包
閉包是由該函數(shù)和其上層執(zhí)行上下文共同構(gòu)成(這點(diǎn)稍后我會(huì)說(shuō)明)
閉包是什么佛南,我們說(shuō)清楚了梗掰,下面我們看下閉包是如何產(chǎn)生的。
接下來(lái)嗅回,我默認(rèn)你已經(jīng)讀過(guò)我之前的兩篇文章 原來(lái)JavaScript內(nèi)部是這樣運(yùn)行的 和 徹底搞懂JavaScript作用域 , 建議先進(jìn)行閱讀理解JS執(zhí)行機(jī)制和作用域等相關(guān)知識(shí)及穗,再理解閉包,否則可能會(huì)理解的不透徹绵载。
針對(duì)上面我提到的幾點(diǎn)進(jìn)行下說(shuō)明:
上述第二點(diǎn)(閉包和詞法作用域埂陆,作用域鏈,垃圾回收機(jī)制息息相關(guān))大家應(yīng)該都清楚了
上述第三點(diǎn)娃豹,當(dāng)函數(shù)baz執(zhí)行時(shí)焚虱,閉包才生成
上述第四點(diǎn),閉包是foo懂版,并不是bar鹃栽,很多書(《you dont know JavaScript》《JavaScript高級(jí)程序設(shè)計(jì)》)中,都強(qiáng)調(diào)保存下來(lái)的引用躯畴,即上例中的bar是閉包民鼓,而chrome認(rèn)為被保存下來(lái)的封閉空間foo是閉包,針對(duì)這點(diǎn)我贊同chrome的判斷(僅為自己的理解蓬抄,如有不同意見(jiàn)丰嘉,歡迎來(lái)討論)
閉包的藝術(shù)性
我相信這個(gè)世界上最美的事物往往就存在我們身邊,通常它并不是那么神秘嚷缭,那么不可見(jiàn)饮亏,只是我們?nèi)鄙倭艘浑p發(fā)現(xiàn)美的眼睛耍贾。
生活中,我們抽出一段時(shí)間放慢腳步路幸,細(xì)細(xì)品味我們所過(guò)的每一分每一秒荐开,會(huì)收獲到生活給我們的另一層樂(lè)趣。
閉包也一樣简肴,它不是很神秘誓焦,反而是在我們的程序中隨處可見(jiàn),當(dāng)我們靜下心來(lái)着帽,品味閉包的味道杂伟,發(fā)現(xiàn)它散發(fā)出一種藝術(shù)的美,樸實(shí)仍翰、精巧又不失優(yōu)雅赫粥。
細(xì)想,在我們作用域氣泡模型中予借,作用域鏈讓我們的內(nèi)部bar氣泡能夠"看到"外面的世界越平,而閉包則讓我們的外部作用域能夠"關(guān)注到"內(nèi)部的情況成為可能×槠龋可見(jiàn)秦叛,只要我們?cè)敢猓瑑?nèi)心世界和外面世界是可以相通的瀑粥。
閉包的應(yīng)用的注意事項(xiàng)
閉包挣跋,在JS中絕對(duì)是一個(gè)高貴的存在,它讓很多不可能實(shí)現(xiàn)的代碼成為可能狞换,但是物雖好避咆,也要合理使用,不然不但不能達(dá)到我們想要的效果修噪,有的時(shí)候可能還會(huì)適得其反查库。
內(nèi)存泄漏(Memory Leak)
JavaScript分配給Web瀏覽器的可用內(nèi)存數(shù)量通常比分配給桌面應(yīng)用程序的少,這樣做主要是防止JavaScript的網(wǎng)頁(yè)耗盡全部系統(tǒng)內(nèi)存而導(dǎo)致系統(tǒng)崩潰黄琼。
因此樊销,要想使頁(yè)面具有更好的性能,就必須確保頁(yè)面占用最少的內(nèi)存資源脏款,也就是說(shuō)围苫,我們應(yīng)該保證執(zhí)行代碼只保存有用的數(shù)據(jù),一旦數(shù)據(jù)不再有用弛矛,我們就應(yīng)該讓垃圾回收機(jī)制對(duì)其進(jìn)行回收够吩,釋放內(nèi)存比然。
我們現(xiàn)在都知道了閉包阻止了垃圾回收機(jī)制對(duì)變量進(jìn)行回收丈氓,因此變量會(huì)永遠(yuǎn)存在內(nèi)存中,即使當(dāng)變量不再被使用時(shí),這樣會(huì)造成內(nèi)存泄漏万俗,會(huì)嚴(yán)重影響頁(yè)面的性能湾笛。因此當(dāng)變量對(duì)象不再適用時(shí),我們要將其釋放闰歪。
我們拿上面代碼舉例:
function foo() {
? ? let a = 2;
? ? function bar() {
? ? ? ? console.log( a );
? ? }
? ? return bar;
}
let baz = foo();
baz(); //baz指向的對(duì)象會(huì)永遠(yuǎn)存在堆內(nèi)存中
baz = null; //如果baz不再使用嚎研,將其指向的對(duì)象釋放
關(guān)于內(nèi)存泄漏,推薦 阮一峰老師博客库倘。
閉包的應(yīng)用
1.模塊
一個(gè)模塊應(yīng)該具有私有屬性临扮、私有方法和公有屬性、公有方法教翩。
而閉包能很好的將模塊的公有屬性杆勇、方法暴露出來(lái)。
var myModule = (function (window, undefined) {
let name = "echo";
function getName() {
return name;
}
return {
name,
getName
}
})(window);
console.log( myModule.name ); // echo
console.log( myModule.getName() ); // echo
"return"關(guān)鍵字將對(duì)象引用導(dǎo)出賦值給myModule饱亿,從而應(yīng)用到閉包蚜退。
2.延時(shí)器(setTimeout)、計(jì)數(shù)器(setInterval)
這里簡(jiǎn)單寫一個(gè)常見(jiàn)的關(guān)于閉包的面試題彪笼。
for( var i = 0; i < 5; i++ ) {
setTimeout(() => {
console.log( i );
}, 1000 * i)
}
答案大家都知道:每秒鐘輸出一個(gè)5钻注,一共輸出5次。
那么如何做到每秒鐘輸出一個(gè)數(shù)配猫,以此為0幅恋,1,2泵肄,3佳遣,4呢?
我們這里只介紹閉包的解決方法凡伊,其他類似塊作用域等等的解決方法零渐,我們這里不討論。
for( var i = 0; i < 5; i++ ) {
((j) => {
setTimeout(() => {
console.log( j );
}, 1000 * j)
})(i)
}
"setTimeout"方法里應(yīng)用了閉包系忙,使其內(nèi)部能夠記住每次循環(huán)所在的詞法作用域和作用域鏈诵盼。
由于setTimeout中的回調(diào)函數(shù)會(huì)在當(dāng)前任務(wù)隊(duì)列的尾部進(jìn)行執(zhí)行,因此上面第一個(gè)例子中每次循環(huán)中的setTimeout回調(diào)函數(shù)記住的i的值是for循環(huán)作用域中的值银还,此時(shí)都是5风宁,而第二個(gè)例子記住的i的數(shù)為setTimeout的父級(jí)作用域自執(zhí)行函數(shù)中的j的值,依次為0蛹疯,1戒财,2,3捺弦,4饮寞。
3.監(jiān)聽器
var oDiv = document.querySeletor("#div");
oDiv.addEventListener('click', function() {
console.log( oDiv.id );9
}
如果此文章對(duì)各位有用
歡迎大家繼續(xù)關(guān)注