正如標(biāo)題所示,JavaScript閉包對我來說一直有點(diǎn)神秘太示,我讀過許多相關(guān)的文章巷蚪,也在工作中使用過閉包,有時甚至用了閉包而不自知祭务。
最近我去參加了一個講座,最終有人以一種我能理解的方式詮釋了它怪嫌。我會盡量在這篇文章中以這種方式來解釋閉包义锥。我要感謝在CodeSmith上的那些了不起的人,以及他們的 JavaScript難懂系列岩灭。
開篇語
在你能對閉包心領(lǐng)神會之前有些概念是你需要知道的拌倍,其一就是執(zhí)行上下文。
這篇文章是很好的執(zhí)行上下文的入門文章噪径,引用一段:
當(dāng)代碼在JavaScript中運(yùn)行時柱恤,它運(yùn)行的環(huán)境是很重要的,它可能是以下情況之一:
全局代碼 —— 你的代碼第一次被執(zhí)行的默認(rèn)環(huán)境找爱;
函數(shù)代碼 —— 每當(dāng)執(zhí)行流進(jìn)入到函數(shù)體中時梗顺;
(...)
(...),我們可以把術(shù)語執(zhí)行上下文視為評估當(dāng)前代碼的環(huán)境
另一方面缴允,我們是在全局的執(zhí)行上下文中啟動程序的荚守,一些變量是在全局的執(zhí)行上下文中聲明的,我們稱之為全局變量练般。當(dāng)程序調(diào)用函數(shù)時矗漾,發(fā)生了什么呢?以下幾步:
- JavaScript創(chuàng)建一個新的局部執(zhí)行上下文薄料;
- 該局部執(zhí)行上下文有它自己的變量集合敞贡,這些變量對這個執(zhí)行上下文來說是本地的;
- 這個新的執(zhí)行上下文被扔到執(zhí)行棧中摄职。把執(zhí)行棧想象成一個持續(xù)追蹤程序執(zhí)行的機(jī)制誊役。
函數(shù)什么時候運(yùn)行結(jié)束呢?當(dāng)遇到 return
或者關(guān)閉符號 }
時谷市。當(dāng)函數(shù)結(jié)束時蛔垢,會發(fā)生以下幾步:
- 局部執(zhí)行上下文從執(zhí)行棧中移除;
- 函數(shù)發(fā)送返回值給調(diào)用的上下文迫悠,調(diào)用上下文是調(diào)用這個函數(shù)的執(zhí)行上下文鹏漆,它可能是全局的執(zhí)行上下文或另一個局部執(zhí)行上下文。此時是由調(diào)用執(zhí)行上下文來處理返回值的。返回值可能是對象艺玲,數(shù)組括蝠,函數(shù),boolean值什么的饭聚。如果函數(shù)沒有
return
聲明忌警,則會返回undefined
。 - 銷毀局部執(zhí)行上下文秒梳。這很重要法绵,銷毀。所有在這個局部執(zhí)行上下文中聲明的變量都會被釋放端幼,它們將失效礼烈。這也是稱它們?yōu)榫植孔兞康脑颉?/li>
一個很基礎(chǔ)的示例
講閉包之前,我們先看下下面這段代碼婆跑,它看起來很直觀,任何在看這篇文章的人應(yīng)該都知道這代碼在干嘛吧庭呜。
1 let a = 3;
2 function addTwo(x) {
3 let ret = x +2 ;
4 return ret;
5 }
6 let b = addTwo(a);
7 console.log(b);
為了理解JavaScript引擎到底是如何工作的滑进,我們來分解一下:
- 第一行我們在全局執(zhí)行上下文中聲明了一個新的變量
a
并賦值為數(shù)字3
; - 接下來就有點(diǎn)棘手了,第二行到第五行是一體的募谎,這里發(fā)生了什么扶关?我們在全局執(zhí)行上下文中聲明了一個名為
addTwo
的變量,并把它分配給了函數(shù)定義数冬,不管{ }
里面的是什么节槐,它們都只屬于addTwo。函數(shù)里面的代碼沒有被評估拐纱,沒有被執(zhí)行铜异,只是存在變量中以供將來使用; - 現(xiàn)在來看第六行秸架。它看起來很簡單揍庄,但這里有太多要分解的地方了。首先我們在全局執(zhí)行上下文中聲明了一個新的變量并標(biāo)識為
b
东抹。一旦一個變量被聲明它就有了undefined
值蚂子; - 接下來,還是在第六行缭黔,我們看到一個賦值運(yùn)算符食茎,準(zhǔn)備給變量
b
分配一個新值。接下來是一個函數(shù)被調(diào)用馏谨,當(dāng)你看到一個變量后面跟著括號()
别渔,就表明這個函數(shù)被調(diào)用了。每個函數(shù)都會有返回值(要不就是一個值,對象钠糊,要不就是undefined)挟秤,不管這個函數(shù)返回什么都將賦值給b
; - 但首先我們要先調(diào)用名為
addTwo
的函數(shù),JavaScript將會在全局執(zhí)行上下文內(nèi)存中查找名為addTwo
的變量抄伍,噢~看吶艘刚,找到一個,它是在第二行聲明的截珍。變量addTwo
包含了一個函數(shù)定義攀甚,變量a
被當(dāng)作一個參數(shù)傳給了該函數(shù)。JavaScript又在全局執(zhí)行上下文內(nèi)存中查找變量a
岗喉,找到了秋度,并且它的值是3,然后把數(shù)字3作為參數(shù)傳給函數(shù)钱床,已經(jīng)準(zhǔn)備好執(zhí)行函數(shù)了荚斯; - 現(xiàn)在執(zhí)行上下文將改變。一個新的局部執(zhí)行上下文被創(chuàng)建了查牌,我們暫且把它稱為addTwo 執(zhí)行上下文事期,這個執(zhí)行上下文被推到調(diào)用堆棧中,在局部執(zhí)行上下文中做的第一件事是什么呢纸颜?
- 你可能會說 “在局部執(zhí)行上下文中聲明了一個新變量
ret
” 兽泣。這是不對的,正確答案應(yīng)該是胁孙,先檢查函數(shù)的參數(shù)唠倦。在局部執(zhí)行上下文中聲明新變量x
,直到3
被當(dāng)作參數(shù)傳過來涮较,變量x被賦值為3稠鼻; - 下一步是:在局部執(zhí)行上下文中聲明新變量
ret
。它的值現(xiàn)在是undefined; - 還是第三行法希,這有個加法的操作要執(zhí)行枷餐。首先我們需要
x
的值,JavaScript會查找變量x
.它會先在局部執(zhí)行上下文中查找苫亦,然后找到了毛肋,值為3
;第二個操作數(shù)是數(shù)字2
屋剑,執(zhí)行加法得出的結(jié)果5被賦值給變量ret
; - 第四行润匙,返回了變量
ret
的內(nèi)容,另一個局部執(zhí)行上下文的查找發(fā)現(xiàn)ret
包含數(shù)字5
唉匾,然后函數(shù)返回數(shù)字5
孕讳,函數(shù)執(zhí)行結(jié)束; - 第四行和第五行匠楚,函數(shù)終止。局部執(zhí)行上下文被銷毀厂财,包括變量
x
和ret
芋簿,不復(fù)存在,該上下文從調(diào)用堆棧中移出并返回給調(diào)用上下文一個返回值璃饱。在這個案例中調(diào)用上下文是全局執(zhí)行上下文与斤,因為函數(shù)addTwo
是在全局執(zhí)行上下文中調(diào)用的; - 現(xiàn)在我們回過頭看一下第四步荚恶,返回值(數(shù)字5)被賦值給變量
b
撩穿,在這個小程序中我們依舊處于第六行; - 不想說的太細(xì)谒撼,但第七行食寡,變量
b
的內(nèi)容被打印在控制臺中,輸出值為5
廓潜;
這就是這個非常簡單的程序的冗長解釋抵皱,而我們至今還沒有講到閉包,我保證一定會講的辩蛋,但首先我們還要再繞一兩個彎路叨叙。
詞法作用域
我們需要對詞法作用域有一定的了解,看下面這個例子:
1 let vall = 2;
2 function multiplyThis(n){
3 let ret = n* vall;
4 return ret;
5 }
6 let multiplied = multiplyThis(6);
7 console.log('example of scope:', multiplied);
這里的想法是我們的變量既有在局部執(zhí)行上下文中的也有在全局執(zhí)行上下文中的堪澎,JavaScript的一個復(fù)雜點(diǎn)就是如何查找這些變量。如果它在局部執(zhí)行上下文中找不到這個變量味滞,那么它就會去該變量的調(diào)用執(zhí)行上下文中查找樱蛤,如果還沒找到,就一直重復(fù)這個操作剑鞍,直到在全局執(zhí)行上下文中查找(如果在這也沒找到那這個變量就是undefined).跟著分析這個案例將會更清晰明了昨凡,如果你知道作用域是怎么工作的,那你可以跳過這一步蚁署。
- 在全局執(zhí)行上下文中聲明一個新的變量
vall
并賦值為2便脊; - 第二行到第五行聲明了一個新變量multiplyThis,并為其分配了一個函數(shù)定義光戈;
- 第六行哪痰,在全局執(zhí)行上下文中聲明一個新的變量
multiplied
; - 在全局執(zhí)行上下文內(nèi)存中檢索
multiplyThis
變量并將它作為函數(shù)執(zhí)行久妆,將數(shù)字6
作為參數(shù)傳遞晌杰; - 創(chuàng)建函數(shù)調(diào)用等于創(chuàng)建執(zhí)行上下文——創(chuàng)建一個新的局部執(zhí)行上下文;
6.在局部執(zhí)行上下文中筷弦,聲明一個變量n
并賦值為6
肋演;
7.第三行,在局部執(zhí)行上下文中聲明一個變量ret
; - 還是第三行爹殊,對變量
n
和vall
的內(nèi)容值執(zhí)行乘法操作蜕乡;先在局部執(zhí)行上下文中查找變量n
,我們在第六步中聲明了它,它的值是6
梗夸;接著在局部執(zhí)行上下文中查找變量vall
,先檢查一下調(diào)用上下文层玲,發(fā)現(xiàn)調(diào)用上下文是全局執(zhí)行上下文,那就在全局執(zhí)行上下文中查找vall
,看绒瘦,找到了称簿,它是在第一步中聲明的,它的值是2
; - 繼續(xù)看第三行惰帽,把兩數(shù)相乘并賦值給變量
ret
, 6 * 2 = 12.現(xiàn)在ret
的值是12
憨降; - 返回
ret
變量,該局部執(zhí)行上下文被銷毀该酗,包括它的變量ret
和n
授药。變量vall
沒有被銷毀,因為它是在全局執(zhí)行上下文中的呜魄; - 回到第六行悔叽,在調(diào)用上下文中,數(shù)字12被賦值給變量
multiplied
; - 最后在第七行爵嗅,在控制臺展示了變量
multiplied
的值娇澎。
所以在這個示例中,我們要記住函數(shù)有權(quán)限訪問在其調(diào)用上下文中的變量趟庄。專業(yè)名詞就叫做詞法作用域锉试。
一個返回函數(shù)的函數(shù)
在第一個示例中函數(shù)addTwo
返回了一個數(shù)字拖云,之前我們說過函數(shù)是可以返回任何值的絮短,現(xiàn)在我們來看一個返回函數(shù)的函數(shù),這對于理解閉包是必不可少的杉允。
這就是我們要來分析的示例:
1 let val = 7;
2 function createAdder() {
3 function addNumbers(a, b){
4 let ret = a + b;
5 return ret;
6 }
7 return addNumbers
8 }
9 let adder = createAdder();
10 let sum = adder(val, 8);
11 console.log('example of function returning a function: ', sum);
讓我們繼續(xù)一步步分解。
- 第一行繁疤,在全局執(zhí)行上下文中聲明變量
val
并賦值為7
; - 第二到第八行鸣哀,在全局執(zhí)行上下文中聲明了一個名為
createAdder
的函數(shù)定義架忌。第三行到第七行描述了函數(shù)定義的內(nèi)容叹放,和之前一樣,先不進(jìn)入該函數(shù)速那,先把這個函數(shù)定義存在變量createAdder
中捶惜; - 第九行景醇,我們在全局執(zhí)行上下文中聲明了一個名為
adder
的新變量吝岭,undefined
被立即賦值給adder
; - 還是第九行,我們看到了括號
()
纬乍,這時需要執(zhí)行或調(diào)用一個函數(shù)彼城,接下來在全局執(zhí)行上下文的內(nèi)存中查找名為createAdder
的變量诅蝶,發(fā)現(xiàn)它在第二步中創(chuàng)建了,調(diào)用它募壕; - 調(diào)用函數(shù)〉骶妫現(xiàn)在我們來看第二行,一個新的局部執(zhí)行上下文被創(chuàng)建了舱馅,我們可以在這個新的執(zhí)行上下文中創(chuàng)建局部變量缰泡。引擎會把這個新的上下文添加到調(diào)用棧中,這個函數(shù)沒有參數(shù)代嗤,可以直接進(jìn)入函數(shù)體中棘钞;
- 繼續(xù)看第三到第六行,我們聲明了一個新的函數(shù)干毅,在局部執(zhí)行上下文中創(chuàng)建了變量
addNumbers
宜猜,這很關(guān)鍵,addNumbers
只存在于局部執(zhí)行上下文中硝逢,我們把函數(shù)定義存儲在名為addNumbers
的局部變量中姨拥; - 現(xiàn)在來看第七行,我們返回了變量
addNumbers
的內(nèi)容渠鸽,引擎查找名為addNumbers
變量并且找到了叫乌,這是一個函數(shù)定義,沒事徽缚,函數(shù)是可以返回任意值的憨奸,包括函數(shù)定義。所以我們返回了addNumbers
的定義凿试,所有第四到第五行{}
之間的內(nèi)容構(gòu)成了這個函數(shù)定義排宰;這時也把局部執(zhí)行上下文從調(diào)用棧中移除了似芝; -
return
之后,局部執(zhí)行上下文被銷毀额各,變量addNumbers
也一樣国觉,但函數(shù)聲明還是存在的,它是從函數(shù)中返回的并且被分配給了變量adder
,就是我們在第三步中創(chuàng)建的變量虾啦; - 現(xiàn)在看第十行麻诀,在全局執(zhí)行上下文中聲明了變量
sum
,暫時分配的值是undefined
; - 接下來就要執(zhí)行一個函數(shù)了傲醉,哪個函數(shù)呢蝇闭?在名為
adder
的變量中定義的函數(shù)。我們先在全局執(zhí)行上下文中查找它硬毕,毫無疑問能找到呻引,這是一個需要兩個參數(shù)的函數(shù); - 先來檢索這兩個參數(shù)吐咳,以便調(diào)用這個函數(shù)并正確傳參逻悠;第一個是我們在第一步中定義的變量
val
,它代表的值是7
韭脊;第二個是數(shù)字8
童谒; - 現(xiàn)在可以執(zhí)行這個函數(shù)了,函數(shù)的定義在第三到第五行沪羔,然后一個新的局部執(zhí)行上下文又被創(chuàng)建了饥伊,在這個局部上下文中創(chuàng)建了兩個變量
a
和b
,它們分別被賦值為7
和8
蔫饰,這就是上一步中我們傳遞給函數(shù)的參數(shù)琅豆; - 第四行,一個名為
ret
的新變量在局部執(zhí)行上下文中被聲明; - 第四行篓吁,執(zhí)行了一個變量
a
和b
相加的加法操作茫因,得出的結(jié)果15
被賦值給ret
變量; - 函數(shù)返回了
ret
變量的值,局部執(zhí)行上下文被摧毀杖剪,并從調(diào)用棧中移除节腐,變量a
、b
摘盆、ret
都不復(fù)存在; - 返回值被賦值給我們在第九步中定義的變量
sum
; - 在控制臺中打印了
sum
的值饱苟;
正如所料控制臺會打印15孩擂,我們在這真的繞了一個挺大圈子的,我只是想闡述以下幾點(diǎn):
第一箱熬,函數(shù)定義可以被存儲在變量中类垦,且在它被調(diào)用之前對程序來說都是不可見狈邑;
第二,每當(dāng)一個函數(shù)被調(diào)用蚤认,就會創(chuàng)建一個局部執(zhí)行上下文米苹,當(dāng)函數(shù)結(jié)束時該局部執(zhí)行上下文也會消失;當(dāng)函數(shù)體遇到return
或 關(guān)閉的大括號 }
時函數(shù)就結(jié)束了砰琢;
閉包
看下面一段代碼并猜一下會發(fā)生什么蘸嘶。
1 function createCounter() {
2 let counter = 0;
3 const myFunction = function () {
4 counter = counter +1;
5 return counter;
6 }
7 return myFunction;
8 }
9 const increment = createCounter();
10 const c1 = increment ();
11 const c2 = increment ();
12 const c3 = increment ();
13 console.log('example increment', c1, c2, c3);
現(xiàn)在我們有了前面兩個例子的經(jīng)驗,來快速瀏覽一下這段代碼的執(zhí)行過程陪汽,就如我們期望的那樣運(yùn)行:
- 第一到八行训唱,在全局執(zhí)行上下文中創(chuàng)建了一個新變量
createCounter
,且被分配為函數(shù)定義挚冤; - 第九行况增,在全局執(zhí)行上下文中創(chuàng)建了一個新變量
increment
; - 還是第九行训挡,我們要調(diào)用函數(shù)
createCounter
澳骤,并把返回值賦值給變量increment
; - 第一到第八行澜薄,調(diào)用函數(shù)为肮,創(chuàng)建局部執(zhí)行上下文;
- 第二行表悬,在局部執(zhí)行上下文中聲明一個名為
counter
的變量弥锄,數(shù)字0
被賦值給counter
; - 第三到六行蟆沫,聲明一個名為
myFunction
的變量籽暇,該變量是在局部執(zhí)行上下文中的,該變量的內(nèi)容是另一個在第四到第五行的函數(shù)定義饭庞; - 返回變量
myFunction
的內(nèi)容戒悠,局部執(zhí)行上下文被刪除,myFunction
和counter
也被銷毀舟山,控制權(quán)回到調(diào)用上下文中绸狐; - 第九行,在調(diào)用上下文中(全局執(zhí)行上下文)累盗,
createCounter
的返回值被賦值給increment
寒矿;現(xiàn)在變量increment
包含了一個函數(shù)定義。這個函數(shù)定義就是createCounter
返回的若债,它的標(biāo)簽不再是myFunction
符相,但具有相同的意義,在全局上下文中,它的標(biāo)簽是increment
啊终; - 第十行镜豹,聲明新變量
c1
; - 還是第十行,查找變量
increment
蓝牲,它是一個函數(shù)趟脂,調(diào)用它。它包含了前面返回的一個函數(shù)定義(第四到第五行定義的)例衍; - 創(chuàng)建一個新的執(zhí)行上下文昔期,沒有參數(shù),開始執(zhí)行函數(shù)肄渗;
- 第四行镇眷,
counter = counter + 1
,在局部執(zhí)行上下文中查找counter
的值。我們剛剛創(chuàng)建這個上下文并且沒有聲明任何局部變量翎嫡,在全局執(zhí)行上下文中查找欠动,沒有發(fā)現(xiàn)名為counter
的變量,所以JavaScript執(zhí)行的是counter = undefined + 1
,聲明一個新的局部變量counter
并賦值為1
惑申,undefined
的值是0
; - 第五行具伍,返回
counter
的內(nèi)容值1
,銷毀這個局部執(zhí)行上下文和變量counter
; - 回到第十行圈驼,返回值
1
被賦值給了c1
; - 第十一行人芽,重復(fù)第10-14步,
c2
也被賦值為1绩脆; - 第十二行萤厅,重復(fù)第10-14步,
c3
也被賦值為1靴迫; - 第十三行惕味,打印變量
c1
、c2
玉锌、c3
名挥;
親自試一下看會發(fā)生什么,你會發(fā)現(xiàn)并沒有如我說明的那樣輸出1主守, 1 禀倔,1
,而是輸出了1参淫,2救湖,3
,那是怎么回事呢涎才?
不知何故鞋既,increment
函數(shù)記住了counter
的值,這是怎么實現(xiàn)的呢?
難道counter
是全局執(zhí)行上下文的一部分嗎涛救?試著打印console.log(counter)
得到的是undefined
,所以并不是。
或許當(dāng)你調(diào)用increment
時业扒,它是從自身被創(chuàng)建的函數(shù)(createCounter
)處開始執(zhí)行的检吆?這怎么可能呢,變量increment
包含的是函數(shù)定義程储,而不是生成它的函數(shù)蹭沛,所以也不是這個原因。
所以一定是另一個機(jī)制——閉包章鲤,我們終于講到它了摊灭。
這里講一下閉包的工作原理。無論何時你定義一個新的函數(shù)并把它賦值給一個變量時會保存函數(shù)定義败徊,閉包也是如此帚呼。閉包包含了在創(chuàng)建函數(shù)時作用域內(nèi)的所有變量,這有點(diǎn)像一個背包的作用皱蹦。函數(shù)定義自帶一個小背包煤杀,背包里裝著在創(chuàng)建函數(shù)時作用域內(nèi)的所有變量。
所以上面我們的步驟分析全是錯的沪哺,這次我們再準(zhǔn)確地分析一遍沈自。
1 function createCounter() {
2 let counter = 0;
3 const myFunction = function () {
4 counter = counter +1;
5 return counter;
6 }
7 return myFunction;
8 }
9 const increment = createCounter();
10 const c1 = increment ();
11 const c2 = increment ();
12 const c3 = increment ();
13 console.log('example increment', c1, c2, c3);
- 第一到八行,跟之前一樣辜妓,我們在全局執(zhí)行上下文中創(chuàng)建了一個新的變量
createCounter
并分配為函數(shù)定義枯途; - 第九行,跟之前一樣籍滴,在全局執(zhí)行上下文中創(chuàng)建了一個新變量
increment
酪夷; - 還是第九行,跟之前一樣异逐,我們要調(diào)用函數(shù)
createCounter
捶索,并把返回值賦值給變量increment
; - 第一到第八行灰瞻,跟之前一樣腥例,調(diào)用函數(shù),創(chuàng)建局部執(zhí)行上下文酝润;
- 第二行燎竖,跟之前一樣,在局部執(zhí)行上下文中聲明一個名為
counter
的變量要销,數(shù)字0
被賦值給counter
构回; - 第三到六行,在局部執(zhí)行上下文中聲明一個新的變量
myFunction
,該變量現(xiàn)在是另一個函數(shù)定義(第四到五行)纤掸,此時我們創(chuàng)建了一個閉包并將其作為函數(shù)定義的一部分脐供。閉包包含了作用域中的變量,這個示例中是counter
變量(值為0)借跪; - 第七行政己,返回變量
myFunction
的內(nèi)容,局部執(zhí)行上下文被刪除掏愁,myFunction
和counter
都不存在了歇由。控制權(quán)回到調(diào)用上下文中果港,所以我們返回的是函數(shù)定義和它的閉包沦泌,此時背包中裝著閉包被創(chuàng)建時作用域中的變量; - 第九行辛掠,在調(diào)用上下文中(即全局執(zhí)行執(zhí)行上下文)谢谦,
createCounter
返回的值被賦值給increment
,現(xiàn)在變量increment
包含一個函數(shù)定義(和其閉包)公浪,這個函數(shù)定義就是createCounter
返回的他宛,它的標(biāo)簽不再是myFunction
,但具有相同的意義欠气,在全局上下文中厅各,它是increment
; - 第十行预柒,聲明一個新變量
c1
; - 繼續(xù)看第十行队塘,查找變量
increment
,是一個函數(shù),調(diào)用它宜鸯,它包含一個函數(shù)定義(第四到五行定義的)憔古,還有一個裝著變量的背包; - 創(chuàng)建一個新的執(zhí)行上下文淋袖,不帶參數(shù)鸿市,開始執(zhí)行函數(shù);
- 第四行即碗,
counter = counter + 1
焰情,查找變量counter
,在局部或全局執(zhí)行上下文中查找之前,先檢查一下閉包剥懒,在背包中查找内舟。瞧瞧,閉包里包含了一個名為counter
的變量初橘,它的值為0
验游。在表達(dá)式的最后充岛,它的值被設(shè)置為1
耕蝉,并且它的值被再次存入背包中〈薰#現(xiàn)在閉包中包含一個變量counter
,其值為1
; - 第五行垒在,返回
counter
的內(nèi)容值1
炒俱,銷毀局部執(zhí)行上下文爪膊; - 回到第十行,返回值
1
被賦值給c1
推盛; - 第十一行,重復(fù)第10-14步瘪菌。這次嘹朗,當(dāng)我們查看閉包時师妙,可以看到變量
counter
的值變?yōu)?code>1了。它是在第12步中第四行代碼被設(shè)置的屹培。它的值增加并且在increment
函數(shù)的閉包中存儲的值為2
褪秀,c2
被賦值為2
蓄诽; - 第十二行,重復(fù)第10-14步媒吗,
c3
被賦值為3
仑氛; - 第十三行,打印變量
c1闸英、c2锯岖、c3
。
所以現(xiàn)在我們能理解它的工作原理了自阱,要記住的關(guān)鍵就是當(dāng)聲明函數(shù)的時候嚎莉,它包含了一個函數(shù)定義和一個閉包。閉包就是函數(shù)創(chuàng)建時作用域內(nèi)所有變量的集合沛豌。
你可能會問趋箩,是任何函數(shù)都有閉包嗎赃额,甚至在全局作用域內(nèi)聲明的函數(shù)?是的叫确。全局作用域內(nèi)創(chuàng)建的函數(shù)也會創(chuàng)建閉包跳芳,但是,由于這些函數(shù)是在全局范圍內(nèi)創(chuàng)建的竹勉,因此它們可以訪問全局范圍內(nèi)的所有變量飞盆。與閉包的概念不是真正相關(guān)的。
當(dāng)一個函數(shù)返回函數(shù)時次乓,閉包的概念變得更清晰一點(diǎn)了吓歇。當(dāng)作返回值的那個函數(shù)可以訪問不在全局作用域內(nèi)的函數(shù),但它們僅存在于其閉包中票腰。
沒那么簡單的閉包
有時閉包會在你沒注意時出現(xiàn)城看,你可能看過稱之為局部應(yīng)用的例子,代碼如下:
let c = 4
const addX = x => n => n + x
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d)
如果箭頭函數(shù)不行杏慰,下面的代碼也是等效的:
let c = 4
function addX(x) {
return function(n) {
return n + x
}
}
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d)
我們聲明了一個通用的帶x參數(shù)并返回另一個函數(shù)的函數(shù)addX
测柠。
返回值的參數(shù)也帶一個參數(shù)和變量x
相加。
變量x
是閉包的一部分缘滥,當(dāng)變量addThree
在局部上下文中被聲明的時候轰胁,它被賦值為一個函數(shù)定義和閉包,閉包中包含變量x
;
所以當(dāng) addThree
被調(diào)用執(zhí)行時朝扼,它是可以在其閉包中訪問到變量x
并和當(dāng)作參數(shù)傳過來的變量n
相加返回值的赃阀。
在這個例子中控制臺打印的值會是數(shù)字7
。
結(jié)論
我能一直記得閉包是通過背包類比,當(dāng)一個函數(shù)被創(chuàng)建或被當(dāng)作參數(shù)傳遞或從另一個函數(shù)返回時擎颖,它都帶有一個背包凹耙,并且在背包中裝著聲明函數(shù)時作用域內(nèi)的所有變量。