閉包詳談

放假閑暇之余看了看各大網(wǎng)站,偶然看見了一篇關(guān)于閉包的文章庐杨,個人感覺還是比較好的灵份,所以特來分享一下!

我們先看一個閉包的例子:

function foo() {
    let a = 2;

    function bar() {
        console.log( a );
    }

    return bar;
}

let baz = foo();

baz();

大家肯定都寫過類似的代碼哮洽,相信很多小伙伴也知道這段代碼應(yīng)用了閉包各吨,but, Why does the closure be generated and Where is closure?

來,我們慢慢分析:

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

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

需要明確的幾點(diǎn):

閉包一定是函數(shù)對象(wintercn大大的閉包考證)
閉包和詞法作用域徙融,作用域鏈洒缀,垃圾回收機(jī)制息息相關(guān)
當(dāng)函數(shù)一定是在其定義的作用域外進(jìn)行的訪問時,才產(chǎn)生閉包
閉包是由該函數(shù)和其上層執(zhí)行上下文共同構(gòu)成(這點(diǎn)稍后我會說明)

閉包是什么欺冀,我們說清楚了,下面我們看下閉包是如何產(chǎn)生的。

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

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

let baz = foo();

此時篓冲,JS的作用域氣泡是這樣的:


image.png

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

但是,我們仔細(xì)看圖中粉色的箭頭硬霍,我們將bar的引用指向baz,正是這種引用賦值,阻止了垃圾回收機(jī)制將foo進(jìn)行回收胯甩,從而導(dǎo)致bar的整條作用域鏈都被保存下來皆串。

接下來怜森,baz()執(zhí)行翅萤,bar進(jìn)入執(zhí)行棧,閉包(foo)形成漱凝,此時bar中依舊可以訪問到其父作用域氣泡中的變量a。

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

當(dāng)JS引擎執(zhí)行到這行代碼let baz = foo();時:

image.png

圖中所示撮竿,let baz = foo();已經(jīng)執(zhí)行完髓需,即將執(zhí)行baz();白热,此時Call Stack中只有全局上下文攻臀。

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


image.png

我們可以看到设联,此時bar進(jìn)入Call Stack中悉稠,并且Closure(foo)形成。

針對上面我提到的幾點(diǎn)進(jìn)行下說明:

上述第二點(diǎn)(閉包和詞法作用域,作用域鏈,垃圾回收機(jī)制息息相關(guān))大家應(yīng)該都清楚了
上述第三點(diǎn)瑰抵,當(dāng)函數(shù)baz執(zhí)行時,閉包才生成
上述第四點(diǎn),閉包是foo,并不是bar徒欣,很多書(《you dont know JavaScript》《JavaScript高級程序設(shè)計(jì)》)中,都強(qiáng)調(diào)保存下來的引用始花,即上例中的bar是閉包,而chrome認(rèn)為被保存下來的封閉空間foo是閉包,針對這點(diǎn)我贊同chrome的判斷(僅為自己的理解甚纲,如有不同意見荆隘,歡迎來討論)

閉包的藝術(shù)性

我相信這個世界上最美的事物往往就存在我們身邊,通常它并不是那么神秘,那么不可見积锅,只是我們?nèi)鄙倭艘浑p發(fā)現(xiàn)美的眼睛。

生活中效斑,我們抽出一段時間放慢腳步概行,細(xì)細(xì)品味我們所過的每一分每一秒,會收獲到生活給我們的另一層樂趣伐脖。

閉包也一樣,它不是很神秘近尚,反而是在我們的程序中隨處可見戈锻,當(dāng)我們靜下心來,品味閉包的味道她倘,發(fā)現(xiàn)它散發(fā)出一種藝術(shù)的美开瞭,樸實(shí)、精巧又不失優(yōu)雅。


image.png

細(xì)想驶悟,在我們作用域氣泡模型中,作用域鏈讓我們的內(nèi)部bar氣泡能夠"看到"外面的世界材失,而閉包則讓我們的外部作用域能夠"關(guān)注到"內(nèi)部的情況成為可能痕鳍。可見豺憔,只要我們愿意额获,內(nèi)心世界和外面世界是可以相通的。

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

閉包恭应,在JS中絕對是一個高貴的存在抄邀,它讓很多不可能實(shí)現(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)該讓垃圾回收機(jī)制對其進(jìn)行回收,釋放內(nèi)存西剥。

我們現(xiàn)在都知道了閉包阻止了垃圾回收機(jī)制對變量進(jì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不再使用,將其指向的對象釋放

閉包的應(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)缚去、計(jì)數(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ù)隊(duì)列的尾部進(jìn)行執(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閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異少梁,居然都是意外死亡洛口,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門凯沪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來第焰,“玉大人,你說我怎么就攤上這事妨马⊥伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵烘跺,是天一觀的道長湘纵。 經(jīng)常有香客問我滤淳,道長梧喷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任脖咐,我火速辦了婚禮铺敌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屁擅。我一直安慰自己偿凭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布煤蹭。 她就那樣靜靜地躺著笔喉,像睡著了一般取视。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上常挚,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天作谭,我揣著相機(jī)與錄音,去河邊找鬼奄毡。 笑死折欠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吼过。 我是一名探鬼主播锐秦,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盗忱!你這毒婦竟也來了酱床?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤趟佃,失蹤者是張志新(化名)和其女友劉穎扇谣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闲昭,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡罐寨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了序矩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸯绿。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖簸淀,靈堂內(nèi)的尸體忽然破棺而出瓶蝴,到底是詐尸還是另有隱情,我是刑警寧澤租幕,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布囊蓝,位于F島的核電站,受9級特大地震影響令蛉,放射性物質(zhì)發(fā)生泄漏聚霜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一珠叔、第九天 我趴在偏房一處隱蔽的房頂上張望蝎宇。 院中可真熱鬧,春花似錦祷安、人聲如沸姥芥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凉唐。三九已至庸追,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間台囱,已是汗流浹背淡溯。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留簿训,地道東北人咱娶。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像强品,于是被迫代替她去往敵國和親膘侮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理的榛,服務(wù)發(fā)現(xiàn)琼了,斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持夫晌,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券表伦,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 5,610評論 16 88
  • 特別說明慷丽,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 485評論 0 0
  • 一鳄哭、你不知道的JavaScript 1要糊、作用域 作用域 LHS RHS RHS查詢與簡單地查找某個變量的值別無二...
    頂兒響叮當(dāng)閱讀 346評論 0 0
  • 寫這篇文章時的心情是十分忐忑的,因?yàn)閷τ谖覀兘裉斓闹鹘牵洪]包妆丘,很多小伙伴都寫過關(guān)于它的文章锄俄,相信大家也讀過不少,那...
    前端彭于晏閱讀 333評論 0 0