摘要:由于聲明在函數(shù)內(nèi)部,所以它擁有涵蓋內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以便在以后的任何時(shí)間進(jìn)行引用。盡管本身并不是觀察閉包的恰當(dāng)例子,但他的確創(chuàng)建了一個(gè)封閉的作用域,并且也是最常用來(lái)創(chuàng)建被封閉起來(lái)的閉包的工具。
在講解作用域閉包的內(nèi)容之前,需要對(duì)以下概念有所掌握:
JavaScript具有兩種作用域:全局作用域和函數(shù)作用域,至于塊作用域也不能說(shuō)沒(méi)有,比如說(shuō): try ...catch...語(yǔ)句中,catch分句就是塊作用域,還有with語(yǔ)句等。
ES6中的let關(guān)鍵字,可以用來(lái)在任意代碼塊中聲明變量。
什么事立即執(zhí)行函數(shù)表達(dá)式以及它的作用。
老生常談什么是閉包閉包的概念:函數(shù)可以記住并訪問(wèn)所在的詞法作用域時(shí),即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行,這時(shí)就產(chǎn)生了閉包。
function foo(){ var a = 2; function bar(){ console.log(a); } return bar; } var baz = foo(); baz(); //這就是閉包的效果
函數(shù)bar()的詞法作用域能夠訪問(wèn)foo()的內(nèi)部作用域,然后我們將bar()函數(shù)本身當(dāng)作一個(gè)值進(jìn)行傳遞。在foo()執(zhí)行后,其返回值賦值給變量baz并調(diào)用baz()。
在foo()執(zhí)行后,通常會(huì)期待foo()的整個(gè)內(nèi)部作用于都被銷毀,因?yàn)槲覀冎酪嬗欣厥諜C(jī)制來(lái)釋放不在使用的內(nèi)存空間。由于看上去foo()的內(nèi)容不會(huì)再被使用,所以很自然地會(huì)考慮對(duì)其進(jìn)行回收。
但是,閉包的神奇之處在于可以阻止這件事情發(fā)生。事實(shí)上內(nèi)部作用域依然存在,因此,沒(méi)有被回收。那么是誰(shuí)在使用這個(gè)內(nèi)部作用域呢?當(dāng)然是bar()在使用。
由于bar()聲明在foo()函數(shù)內(nèi)部,所以它擁有涵蓋foo()內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以便bar()在以后的任何時(shí)間進(jìn)行引用。
bar()函數(shù)在foo()調(diào)用完成后,依舊持有對(duì)其作用域的引用,而這個(gè)引用就叫做閉包
當(dāng)然,無(wú)論使用何種方式對(duì)函數(shù)類型的值進(jìn)行傳遞,當(dāng)函數(shù)在別處調(diào)用時(shí)都可以觀察到閉包
function foo(){ var a = 2; function baz(){ console.log(a)//2 } bar(baz); } function bar(fn){ fn(); //這就是閉包 }
相比于上面代碼的枯燥,這有一個(gè)更加常見(jiàn)的例子
function wait(message){ setTimeout(function time(){ console.log(message); }, 1000); } wait("hello clousre");
簡(jiǎn)單分析一下這段代碼:我們將一個(gè)名為time的內(nèi)部函數(shù)傳遞給setTimeout(),time具有涵蓋wait()作用域的閉包,因此,還保有對(duì)變量message的引用。
wait(..)執(zhí)行1000ms后,它的內(nèi)部作用域并不會(huì)消失,time()函數(shù)依舊保有對(duì)wait()作用域的閉包,在引擎內(nèi)部,內(nèi)置的工具函數(shù)setTimeout()會(huì)持有一個(gè)對(duì)參數(shù)的引用,這個(gè)參數(shù)也許叫作fn或者func之類的。引擎會(huì)調(diào)用這個(gè)函數(shù),而詞法作用域在這個(gè)過(guò)程中保持完整。
這就是閉包
那么閉包有哪些應(yīng)用呢?其實(shí)包括定時(shí)器,事件監(jiān)聽器,Ajax請(qǐng)求,跨窗口通信,Web Workers或者任何其他的異步(或者同步)任務(wù)中,只要使用回掉函數(shù),實(shí)際上就是在使用閉包!
這里我們?cè)倏匆粋€(gè)特別典型閉包的例子,但嚴(yán)格來(lái)說(shuō)它并不是閉包。
var a = 2; (function IIFE(){ console.log(a) })();
IIFE即立即執(zhí)行函數(shù)表達(dá)式,第一個(gè)()讓函數(shù)變?yōu)楹瘮?shù)表達(dá)式,第二個(gè)()函數(shù)執(zhí)行。為什么說(shuō)他嚴(yán)格上來(lái)講并不是閉包呢?因?yàn)樵谑纠a中函數(shù)并不是在它本身的詞法作用域之外執(zhí)行的,它在其定義時(shí)所在的作用域執(zhí)行,a是通過(guò)詞法作用域查找到的,并不是閉包發(fā)現(xiàn)的。
盡管IIFE本身并不是觀察閉包的恰當(dāng)例子,但他的確創(chuàng)建了一個(gè)封閉的作用域,并且也是最常用來(lái)創(chuàng)建被封閉起來(lái)的閉包的工具。
說(shuō)到閉包我們接觸最早的也許就是for循環(huán)的例子:
for(var i = 1; i<6; i++){ setTImeout(function time(){ console.log(i) }, i*1000) }
記得第一次看見(jiàn)這段代碼的時(shí)候,那是被深深的虐到,作為C語(yǔ)言起手的同學(xué),當(dāng)時(shí)真的是一臉的懵逼,為什么會(huì)輸出5個(gè)6, 為什么會(huì)輸出5個(gè)6,為什么?當(dāng)時(shí)其他人的講解也是模模糊糊的,雖然提出了解決方法,當(dāng)還是無(wú)法理解這其中的機(jī)制原理,所以,我痛下決心把它弄懂!也許只有我不懂吧!
問(wèn):為什么會(huì)輸出66666呢?
答:能輸出66666說(shuō)明for循環(huán)內(nèi)部的代碼的確執(zhí)行了5次。
問(wèn):那6是從哪來(lái)的呢?
答:6是我們循環(huán)的終止條件,所以輸出6。
問(wèn):那為什么不是循環(huán)一次,輸出一個(gè)值, 1,2,3,4,5這樣呢?
答:setTimeout()函數(shù)是在循環(huán)結(jié)束時(shí)執(zhí)行的,就算是你設(shè)置setTimeout(fn, 0),它也是在for循環(huán)完成后立即執(zhí)行,總之就是在for循環(huán)執(zhí)行完成后才執(zhí)行。
好了,這就不難理解了為什么會(huì)輸出66666了。但這也就引出了一個(gè)更深入的話題,代碼中到底什么缺陷導(dǎo)致它的行為同語(yǔ)義暗示的不一致呢?
缺陷是:我們?cè)噲D假設(shè)循環(huán)中的每個(gè)迭代在運(yùn)行時(shí)都會(huì)給自己“捕獲”一個(gè)i的副本。但是根據(jù)作用域的工作原理,實(shí)際情況是盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的,但是它們都被封閉在一個(gè)共享的全局作用域中,因此實(shí)際上只有一個(gè)i。所以,實(shí)際的樣子是這樣。
而我們想象中的樣子確是這樣。
下面回到正題。既然明白了缺陷是什么,那么要怎樣做才能達(dá)到我們想象中的樣子呢?答案是我們需要在每一次迭代的過(guò)程中都創(chuàng)建一個(gè)閉包作用域。在上文中我們已經(jīng)有所鋪墊,IIFE會(huì)通過(guò)聲明立即執(zhí)行一個(gè)函數(shù)來(lái)創(chuàng)建作用域。so我們可以將代碼改成下面的樣子:
for(var i=1; i<6; i++){ (function(){ setTImeout(function time(){ console.log(i) }, i*1000) })(); }
這樣每一次迭代我們都創(chuàng)建了一個(gè)封閉的作用域(你可以想象為上圖中黃色的矩形部分)。但是這樣做仍舊不行,為什么呢?因?yàn)殡m然每個(gè)延遲函數(shù)都會(huì)將IIFE在每次迭代中創(chuàng)建的作用域封閉起來(lái),但我們封閉的作用域是空的,所以必須傳點(diǎn)東西過(guò)去才能實(shí)現(xiàn)我們想要的結(jié)果。
for(var i=1; i<6; i++){ (function(){ var j = i setTImeout(function time(){ console.log(j) }, j*1000) })(); }
ok!試試現(xiàn)在他能正常工作嗎?對(duì)這段代碼再進(jìn)行一點(diǎn)改進(jìn)
for(var i=1; i<6; i++){ (function(j){ setTImeout(function time(){ console.log(j) }, j*1000) })(i); }
總的來(lái)說(shuō),就是在迭代內(nèi)使用IIFE會(huì)為每個(gè)迭代都生成一個(gè)新的作用域,使得延遲函數(shù)可以將新的作用域封閉在每個(gè)迭代內(nèi)部,我們同時(shí)在迭代的過(guò)程中將每次迭代的i值作為參數(shù)傳入進(jìn)新的作用域,這樣在迭代中創(chuàng)建的封閉作用域就都會(huì)含有一個(gè)具有正確值的變量供我們?cè)L問(wèn)。ok,it"s work!
塊作用域仔細(xì)思考我們前面的解決方案。我們使用IIFE在每次迭代時(shí)都創(chuàng)建一個(gè)新的作用域。也就是說(shuō),每次迭代我們都需要一個(gè)塊作用域。前面我們提到,你需要對(duì)ES6中的let關(guān)鍵字進(jìn)行了解,它可以用來(lái)劫持塊作用域,并且在這個(gè)塊作用域中聲明一個(gè)變量。
本質(zhì)上來(lái)講它是將一個(gè)塊轉(zhuǎn)換成可以被關(guān)閉的作用域。
for(var i=1; i<6; i++){ let j = i; //閉包的塊作用域 setTImeout(function time(){ console.log(j) }, j*1000) }
如果將let聲明在for循環(huán)的頭部那么將會(huì)有一些特殊的行為,有多特殊呢?它會(huì)指出變量在循環(huán)過(guò)程中不止被聲明一次,每次迭代都會(huì)聲明。隨后的每個(gè)迭代都會(huì)使用上一個(gè)迭代結(jié)束時(shí)的值來(lái)初始化這個(gè)變量。不管這句話有多拗口,看看代碼吧!
for(let i=1; i<6; i++){ setTImeout(function time(){ console.log(i) }, i*1000) }
有沒(méi)有似曾相識(shí)的感覺(jué),有沒(méi)有感動(dòng)到,我已經(jīng)老淚縱橫了。。。
下一節(jié)講閉包運(yùn)用--模塊機(jī)制
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/79010.html
摘要:一言以蔽之,閉包,你就得掌握。當(dāng)函數(shù)記住并訪問(wèn)所在的詞法作用域,閉包就產(chǎn)生了。所以閉包才會(huì)得以實(shí)現(xiàn)。從技術(shù)上講,這就是閉包。執(zhí)行后,他的內(nèi)部作用域并不會(huì)消失,函數(shù)依然保持有作用域的閉包。 網(wǎng)上總結(jié)閉包的文章已經(jīng)爛大街了,不敢說(shuō)筆者這篇文章多么多么xxx,只是個(gè)人理解總結(jié)。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結(jié)與《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:也正因?yàn)檫@個(gè)閉包的特性,閉包函數(shù)可以讓父函數(shù)的數(shù)據(jù)一直駐留在內(nèi)存中保存,從而這也是后來(lái)模塊化的基礎(chǔ)。只有閉包函數(shù),可以讓它的父函數(shù)作用域永恒,像全局作用域,一直在內(nèi)存中存在。的本質(zhì)就是如此,每個(gè)模塊文件就是一個(gè)大閉包。 為什么會(huì)有閉包 js之所以會(huì)有閉包,是因?yàn)閖s不同于其他規(guī)范的語(yǔ)言,js允許一個(gè)函數(shù)中再嵌套子函數(shù),正是因?yàn)檫@種允許函數(shù)嵌套,導(dǎo)致js出現(xiàn)了所謂閉包。 function...
摘要:曾幾何時(shí),閉包好像就是一個(gè)十分難以捉摸透的東西,看了很多文章,對(duì)閉包都各有說(shuō)法,以致讓我十分暈,什么內(nèi)部變量外部變量的,而且大多數(shù)都只描述一個(gè)過(guò)程,沒(méi)有給閉包的定義,最后,舉幾個(gè)例子,告訴你這就是閉包。 曾幾何時(shí),閉包好像就是一個(gè)十分難以捉摸透的東西,看了很多文章,對(duì)閉包都各有說(shuō)法,以致讓我十分暈,什么內(nèi)部變量、外部變量的,而且大多數(shù)都只描述一個(gè)過(guò)程,沒(méi)有給閉包的定義,最后,舉幾個(gè)例子...
摘要:內(nèi)部的稱為內(nèi)部函數(shù)或閉包函數(shù)。過(guò)度使用閉包會(huì)導(dǎo)致性能下降。,閉包函數(shù)分為定義時(shí),和運(yùn)行時(shí)。循環(huán)會(huì)先運(yùn)行完畢,此時(shí),閉包函數(shù)并沒(méi)有運(yùn)行。閉包只能取得外部函數(shù)中的最后一個(gè)值。事件綁定種的匿名函數(shù)也是閉包函數(shù)。而對(duì)象中的閉包函數(shù),指向。 閉包概念解釋: 閉包(也叫詞法閉包或者函數(shù)閉包)。 在一個(gè)函數(shù)parent內(nèi)聲明另一個(gè)函數(shù)child,形成了嵌套。函數(shù)child使用了函數(shù)parent的參數(shù)...
摘要:所以,有另一種說(shuō)法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。所以本文中將以維基百科中的定義為準(zhǔn)即在計(jì)算機(jī)科學(xué)中,閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。 閉包(closure)是JavaScript中一個(gè)神秘的概念,許多人都對(duì)它難以理解,我也一直處于似懂非懂的狀態(tài),前幾天深入了解了一下執(zhí)行環(huán)境以及作用域鏈,可戳查看詳情,而閉包與作用域及作用域鏈的關(guān)系密不可分,所...
閱讀 1148·2021-11-25 09:43
閱讀 2966·2019-08-30 15:54
閱讀 3350·2019-08-30 15:54
閱讀 2992·2019-08-30 15:44
閱讀 1624·2019-08-26 12:18
閱讀 2256·2019-08-26 11:42
閱讀 876·2019-08-26 11:35
閱讀 3296·2019-08-23 18:22