閉包

相信眾多JS的lovers都聽說過這句話:閉包很重要但是很難理解戏羽。

先看一個閉包的例子:

function foo() {

? ? let a = 2;

? ? function bar() {

? ? ? ? console.log( a );

? ? }

? ? return bar;

}

let baz = foo();

baz();

大家肯定都寫過類似的代碼,相信很多小伙伴也知道這段代碼應(yīng)用了閉包妄讯,我們慢慢分析:

首先必須先知道閉包是什么酷宵,才能分析出閉包為什么產(chǎn)生和閉包到底在哪?

當(dāng)一個函數(shù)能夠記住并訪問到其所在的詞法作用域及作用域鏈炕置,特別強調(diào)是在其定義的作用域外進行的訪問,此時該函數(shù)和其上層執(zhí)行上下文共同構(gòu)成閉包默垄。

需要明確的幾點:

閉包一定是函數(shù)對象(wintercn大大的閉包考證)

閉包和詞法作用域仍劈,作用域鏈寡壮,垃圾回收機制息息相關(guān)

當(dāng)函數(shù)一定是在其定義的作用域外進行的訪問時,才產(chǎn)生閉包

閉包是由該函數(shù)和其上層執(zhí)行上下文共同構(gòu)成(這點稍后我會說明)

閉包是什么这溅,我們說清楚了棒仍,下面我們看下閉包是如何產(chǎn)生的莫其。

接下來,我默認(rèn)你已經(jīng)讀過我之前的兩篇文章 原來JavaScript內(nèi)部是這樣運行的 和 徹底搞懂JavaScript作用域 , 建議先進行閱讀理解JS執(zhí)行機制和作用域等相關(guān)知識浇揩,再理解閉包憨颠,否則可能會理解的不透徹。

現(xiàn)在我假設(shè)JS引擎執(zhí)行到這行代碼

let baz = foo();

這個時候foo函數(shù)已經(jīng)執(zhí)行完养盗,JS的垃圾回收機制應(yīng)該會自動將其標(biāo)記為"離開環(huán)境",等待回收機制下次執(zhí)行适篙,將其內(nèi)存進行釋放(標(biāo)記清除)。

但是聂儒,我們仔細(xì)看圖中粉色的箭頭丹喻,我們將bar的引用指向baz碍论,正是這種引用賦值,阻止了垃圾回收機制將foo進行回收税娜,從而導(dǎo)致bar的整條作用域鏈都被保存下來。

接下來概行,baz()執(zhí)行弧岳,bar進入執(zhí)行棧,閉包(foo)形成涧卵,此時bar中依舊可以訪問到其父作用域氣泡中的變量a腹尖。

這樣說可能不是很清晰热幔,接下來我們借助chrome的調(diào)試工具看下閉包產(chǎn)生的過程。

當(dāng)JS引擎執(zhí)行到這行代碼let baz = foo();時:let baz = foo();已經(jīng)執(zhí)行完近尚,即將執(zhí)行baz();认烁,此時Call Stack中只有全局上下文。

接下來baz();執(zhí)行:

此時bar進入Call Stack中舶沛,并且Closure(foo)形成窗价。

針對上面我提到的幾點進行下說明:

上述第二點(閉包和詞法作用域撼港,作用域鏈,垃圾回收機制息息相關(guān))大家應(yīng)該都清楚了

上述第三點往毡,當(dāng)函數(shù)baz執(zhí)行時靶溜,閉包才生成

上述第四點懒震,閉包是foo嗤详,并不是bar葱色,很多書(《you dont know JavaScript》《JavaScript高級程序設(shè)計》)中,都強調(diào)保存下來的引用办龄,即上例中的bar是閉包舞痰,而chrome認(rèn)為被保存下來的封閉空間foo是閉包响牛,針對這點我贊同chrome的判斷(僅為自己的理解赫段,如有不同意見,歡迎來討論)

閉包不是很神秘贬丛,反而是在我們的程序中隨處可見给涕,當(dāng)我們靜下心來,品味閉包的味道恭应,發(fā)現(xiàn)它散發(fā)出一種藝術(shù)的美耘眨,樸實剔难、精巧又不失優(yōu)雅。

細(xì)想非迹,在我們作用域氣泡模型中纯趋,作用域鏈讓我們的內(nèi)部bar氣泡能夠"看到"外面的世界,而閉包則讓我們的外部作用域能夠"關(guān)注到"內(nèi)部的情況成為可能唇兑。可見蔫耽,只要我們愿意,內(nèi)心世界和外面世界是可以相通的留夜。

閉包的應(yīng)用的注意事項

閉包匙铡,在JS中絕對是一個高貴的存在,它讓很多不可能實現(xiàn)的代碼成為可能碍粥,但是物雖好鳖眼,也要合理使用,不然不但不能達(dá)到我們想要的效果嚼摩,有的時候可能還會適得其反钦讳。

內(nèi)存泄漏(Memory Leak)

JavaScript分配給Web瀏覽器的可用內(nèi)存數(shù)量通常比分配給桌面應(yīng)用程序的少,這樣做主要是防止JavaScript的網(wǎng)頁耗盡全部系統(tǒng)內(nèi)存而導(dǎo)致系統(tǒng)崩潰枕面。

因此愿卒,要想使頁面具有更好的性能,就必須確保頁面占用最少的內(nèi)存資源琼开,也就是說,我們應(yīng)該保證執(zhí)行代碼只保存有用的數(shù)據(jù)枕荞,一旦數(shù)據(jù)不再有用柜候,我們就應(yīng)該讓垃圾回收機制對其進行回收,釋放內(nèi)存躏精。

我們現(xiàn)在都知道了閉包阻止了垃圾回收機制對變量進行回收渣刷,因此變量會永遠(yuǎn)存在內(nèi)存中,即使當(dāng)變量不再被使用時玉控,這樣會造成內(nèi)存泄漏飞主,會嚴(yán)重影響頁面的性能。因此當(dāng)變量對象不再適用時高诺,我們要將其釋放碌识。

我們拿上面代碼舉例:

function foo() {

? ? let a = 2;

? ? function bar() {

? ? ? ? console.log( a );

? ? }

? ? return bar;

}

let baz = foo();

baz(); //baz指向的對象會永遠(yuǎn)存在堆內(nèi)存中

baz = null; //如果baz不再使用,將其指向的對象釋放

關(guān)于內(nèi)存泄漏虱而,推薦 阮一峰老師博客筏餐。

閉包的應(yīng)用

1.模塊

一個模塊應(yīng)該具有私有屬性、私有方法和公有屬性牡拇、公有方法魁瞪。

而閉包能很好的將模塊的公有屬性穆律、方法暴露出來。

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)鍵字將對象引用導(dǎo)出賦值給myModule导俘,從而應(yīng)用到閉包峦耘。

2.延時器(setTimeout)、計數(shù)器(setInterval)

這里簡單寫一個常見的關(guān)于閉包的面試題旅薄。

for( var i = 0; i < 5; i++ ) {

setTimeout(() => {

console.log( i );

}, 1000 * i)

}

答案大家都知道:每秒鐘輸出一個5辅髓,一共輸出5次。

那么如何做到每秒鐘輸出一個數(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ù)會在當(dāng)前任務(wù)隊列的尾部進行執(zhí)行,因此上面第一個例子中每次循環(huán)中的setTimeout回調(diào)函數(shù)記住的i的值是for循環(huán)作用域中的值液荸,此時都是5,而第二個例子記住的i的數(shù)為setTimeout的父級作用域自執(zhí)行函數(shù)中的j的值脱篙,依次為0娇钱,1,2绊困,3文搂,4。

3.監(jiān)聽器

var oDiv = document.querySeletor("#div");

oDiv.addEventListener('click', function() {

console.log( oDiv.id );

})

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秤朗,一起剝皮案震驚了整個濱河市煤蹭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌取视,老刑警劉巖硝皂,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異作谭,居然都是意外死亡稽物,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門折欠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贝或,“玉大人吼过,你說我怎么就攤上這事∵浣保” “怎么了盗忱?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長羊赵。 經(jīng)常有香客問我售淡,道長,這世上最難降的妖魔是什么慷垮? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任揖闸,我火速辦了婚禮,結(jié)果婚禮上料身,老公的妹妹穿的比我還像新娘汤纸。我一直安慰自己,他們只是感情好芹血,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布贮泞。 她就那樣靜靜地躺著,像睡著了一般幔烛。 火紅的嫁衣襯著肌膚如雪啃擦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天饿悬,我揣著相機與錄音令蛉,去河邊找鬼。 笑死狡恬,一個胖子當(dāng)著我的面吹牛珠叔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弟劲,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祷安,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兔乞?” 一聲冷哼從身側(cè)響起汇鞭,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庸追,沒想到半個月后霍骄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡锚国,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年腕巡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片血筑。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡绘沉,死狀恐怖煎楣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情车伞,我是刑警寧澤择懂,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站另玖,受9級特大地震影響困曙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谦去,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一慷丽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鳄哭,春花似錦要糊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至勺拣,卻和暖如春奶赠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背药有。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工毅戈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人塑猖。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓竹祷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親羊苟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容