前言:
問君能有幾多愁,恰似答過閉包后面試官搖搖頭 -- 沃 · 茲?rùn)C(jī)碩德
閉包這個(gè)東西 亡嫌, 面試之中必備的問題之一 嚎于, JavaScript 最關(guān)鍵的底層原理之一 掘而,無(wú)論是從哪個(gè)角度上來(lái)說(shuō),如果想要進(jìn)階為一個(gè)資深jser 于购,那么你都需要完全理解閉包這個(gè)東西∨鬯現(xiàn)在在網(wǎng)上存在非常多的閉包教程,但是我個(gè)人的感覺大多都是些在半路上進(jìn)行講解的教程肋僧,即使背下來(lái)也沒辦法理解斑胜,故而在開發(fā)中或者在面試官連續(xù)的提問之中真的把問題說(shuō)出個(gè)所以然,那么本篇教程的主要目的就是從根源上把閉包的一整套知識(shí)體系梳理給大家嫌吠,所以這篇文章閉包是標(biāo)題止潘,但是我更認(rèn)為這是對(duì)一整套JavaScript程序底層體系的講解。
運(yùn)行一個(gè)函數(shù)
函數(shù)大家肯定熟悉居兆,要是這個(gè)東西你真的不理解的話覆山,請(qǐng)先買本高級(jí)程序看看然后再來(lái)看這篇文章,你以為我要說(shuō)函數(shù) ? 不不不泥栖, 我說(shuō)要說(shuō)的是你知道函數(shù)在被調(diào)用時(shí)發(fā)生了什么不? 這就是一個(gè)函數(shù):
function foo(){
var hello = "hello world";
console.log(hello);
}
// 調(diào)用函數(shù)
foo()
ok , 就這么一段代碼他做了什么簇宽,我不用說(shuō),你也知道吧享,不就是打印了 hello world 的一個(gè)簡(jiǎn)單函數(shù)么魏割,對(duì)但是 too yuang too easy , 你知道咱們?yōu)g覽器的解析器做了些啥不 ? 每段程序的執(zhí)行都是硬件配合軟件最終呈現(xiàn)的結(jié)果 , 這之中有個(gè)非常非常非常重要的東西 內(nèi)存钢颂。
什么是內(nèi)存那 ?
這貨就是內(nèi)存
你所有的代碼在執(zhí)行的時(shí)候都會(huì)被塞進(jìn)這里,在本篇文章中你只需要理解到這里就可以了殊鞭,如果需要繼續(xù)理解我會(huì)再單獨(dú)寫一篇關(guān)于內(nèi)存的專題遭垛。
ok 我們接著來(lái)說(shuō),往里面塞數(shù)據(jù)這個(gè)事操灿,究竟怎么塞锯仪, 內(nèi)部的結(jié)構(gòu)是什么樣的?
棧你可以理解為一個(gè)開口向上的容器趾盐,我們將數(shù)據(jù)在開口處放置進(jìn)去庶喜,數(shù)據(jù)則自然下落到結(jié)構(gòu)底部,所以數(shù)據(jù)在棧中的順序是從下至上的救鲤,從底至上的數(shù)據(jù)下標(biāo)分別為 0 久窟, 1 , 2 ... 當(dāng)數(shù)據(jù)已經(jīng)用完了本缠,程序發(fā)出指令需要清空棧內(nèi)存之中的數(shù)據(jù)時(shí)我們刪除的順序是自頂部向下刪除的斥扛,順序?yàn)?... 2 , 1 , 0 。由此我們可以得到規(guī)律棧內(nèi)存數(shù)據(jù)我們遵循的數(shù)據(jù)是先進(jìn)來(lái)的數(shù)據(jù)最后釋放出去丹锹,我們稱此為 FILO (first in last out), 簡(jiǎn)稱先后出稀颁。如果你感覺這個(gè)文字看起來(lái)非常難以理解队他,請(qǐng)看這個(gè)動(dòng)圖,動(dòng)圖中黑色的為棧結(jié)構(gòu)峻村,紅色的表示數(shù)據(jù)麸折,能更直觀的幫你去理解這個(gè)結(jié)構(gòu),當(dāng)然內(nèi)存并不只是有一種結(jié)構(gòu)還有一種樹形結(jié)構(gòu)叫做堆粘昨,我們?cè)诖瞬毁樖銎湓砉柑洌驗(yàn)闀簳r(shí)用不到。
@w=200h=200
活動(dòng)對(duì)象-AO(Active Object)
上文說(shuō)到一個(gè)非常重要的概念张肾,就是所有程序在執(zhí)行時(shí)都會(huì)和內(nèi)存產(chǎn)生關(guān)聯(lián)芭析,JavaScript之中函數(shù)執(zhí)行的時(shí)候也會(huì)和內(nèi)存產(chǎn)生很多關(guān)聯(lián), 這個(gè)關(guān)聯(lián)我們現(xiàn)在也只關(guān)注一點(diǎn)吞瞪,就是函數(shù)執(zhí)行時(shí)我們內(nèi)存里面會(huì)儲(chǔ)存些啥馁启。
當(dāng)函數(shù)執(zhí)行的時(shí)候,我們的程序?yàn)榱擞涗浐瘮?shù)內(nèi)部的變量芍秆,所以會(huì)找一塊空間對(duì)這部分?jǐn)?shù)據(jù)進(jìn)行一個(gè)臨時(shí)的存儲(chǔ)惯疙,在使用結(jié)束之后,如果沒用了就刪除掉這部分?jǐn)?shù)據(jù)妖啥。這部分?jǐn)?shù)據(jù)如果我們用JavaScript進(jìn)行表達(dá)的話霉颠,最好的結(jié)構(gòu)就是對(duì)象,我們以對(duì)象的形式對(duì)其進(jìn)行存儲(chǔ)和表示 :
我們?cè)儆么a去說(shuō)明一下:
function foo(){
var hello = "hello world";
console.log(hello);
}
foo()
// 只要是foo() 這段代碼被解析器解析了荆虱,那么調(diào)用內(nèi)存這事就開始了蒿偎。
// 1. 解析器會(huì)在內(nèi)存之中創(chuàng)建一個(gè)臨時(shí)活動(dòng)對(duì)象:
var _temp_Active_Object_foo = {}
// 2. 解析器在解析內(nèi)部代碼 var hello = "hello world"時(shí),會(huì)在這個(gè)對(duì)象上添加一個(gè)屬性怀读,hello 值為 "hello world";
_temp_Active_Object_foo.hello = "hello world";
// 3. 解析器在解析內(nèi)部代碼console.log(hello),檢索到了hello變量的引用诉位,雖然看起來(lái)我們是在使用hello變量可是實(shí)際上我們引用的是活動(dòng)對(duì)象之中的屬性:
console.log(_temp_Active_Object_foo.hello);
//4. 函數(shù)執(zhí)行完畢,在棧內(nèi)存之中釋放掉這個(gè)活動(dòng)對(duì)象
delete window._temp_Active_Object_foo
好了菜枷,這下我們把這個(gè)內(nèi)容大概說(shuō)清楚了苍糠,如果沒有看明白建議仔細(xì)閱讀流程圖和代碼,直到你把這兩部分的內(nèi)容完全記憶下來(lái)犁跪,這樣你就可以繼續(xù)進(jìn)行下面的學(xué)習(xí)了椿息,你距離學(xué)會(huì)整套閉包的體系還有一步之遙歹袁。
兩個(gè)函數(shù)的嵌套
ok,根據(jù)前面的知識(shí)講解坷衍,我們已經(jīng)明白了一個(gè)函數(shù)執(zhí)行起來(lái)配合內(nèi)存會(huì)做哪些事情,可是總會(huì)感覺學(xué)習(xí)這些東西有啥用条舔,其實(shí)你可以把這些知識(shí)模塊當(dāng)成是一個(gè)又一個(gè)的樂高積木我們正努力把這些東西組裝成我們想要的結(jié)構(gòu)枫耳。
回到主題,我們現(xiàn)在要去研究的就是兩個(gè)函數(shù)的嵌套孟抗,外部函數(shù)我們?nèi)∶?outer , 內(nèi)部函數(shù)我們?nèi)∶鹖nner :
function outer(){
function inner(){
}
}
outer()
結(jié)構(gòu)上大體定在這里迁杨,我們根據(jù)上面所學(xué)簡(jiǎn)單發(fā)散一下我們的思維钻心,在外部函數(shù)執(zhí)行的時(shí)候我們做了哪些事情。
function outer(){
function inner(){
}
}
outer()
//1.在內(nèi)存之中創(chuàng)建一個(gè)活動(dòng)對(duì)象
var _temp_Active_Object_outer = {};
//2.在這個(gè)活動(dòng)對(duì)象上去放一個(gè)屬性
_temp_Active_Object_outer.inner = function(){}
總結(jié)下這段代碼 : 一個(gè)活動(dòng)對(duì)象之中放入了一個(gè)函數(shù)(引用類型)铅协,而不再是具體的值 捷沸, 這又有什么關(guān)系那。你可不能忘了狐史,這個(gè)活動(dòng)對(duì)象是怎么生成的痒给,是由函數(shù)生成的,也就是說(shuō)這個(gè)活動(dòng)對(duì)象之中存在一個(gè)可以創(chuàng)建活動(dòng)對(duì)象的玩意骏全〔园兀可以這么理解,平日里你家人給你發(fā)生活費(fèi)的時(shí)候就一個(gè)紅包姜贡,點(diǎn)一下收一次錢就可以了试吁,這次給你發(fā)的紅包點(diǎn)完開之后不僅顯示收錢了,又給你彈出一個(gè)新的紅包楼咳,上面寫著個(gè)開熄捍,你要不要點(diǎn) ? 點(diǎn)了紅包就打開了潘多拉魔盒,發(fā)現(xiàn)了新大陸母怜,原來(lái)紅包還可以這么玩治唤?
是啊,函數(shù)還可以這么玩糙申。
好的讓我們?cè)龠M(jìn)一步 :
function outer(){
//新增一個(gè)外部函數(shù)中的變量
var freeVar = "something"
function inner(){
//在內(nèi)部函數(shù)引用freeVar
console.log(freeVar);
}
// 最后將這個(gè)結(jié)果返回
return inner
}
//在外部接受代碼
var inner = outer()
其實(shí)在這個(gè)時(shí)候宾添,我們已經(jīng)使用了閉包,為什么那柜裸?簡(jiǎn)單來(lái)說(shuō)閉包其實(shí)就是:
- 在外部函數(shù)聲明的變量在內(nèi)部函數(shù)被引用了
- 在外部函數(shù)的時(shí)候缕陕,把內(nèi)部函數(shù)返回出去,在全局接收疙挺。
簡(jiǎn)單的理解先到這里扛邑,下次我會(huì)談一談關(guān)于閉包的實(shí)際應(yīng)用及學(xué)習(xí)內(nèi)存部分知識(shí)的意義。