javascript
《你不知道的JavaScript》-- 精读(五)
知識(shí)點(diǎn)
1.實(shí)質(zhì)問題
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。
function foo(){var a = 2;function bar(){console.log(a); // 2}bar() } foo() 復(fù)制代碼根據(jù)前面的定義,嚴(yán)格來說上述代碼并不是閉包,最準(zhǔn)確地用來解釋bar()對(duì)a的引用的方法是詞法作用域的查找規(guī)則,而這些規(guī)則只是閉包的一部分。
function foo(){var a = 2;function bar(){console.log(a);}return bar; } var baz = foo(); baz(); // 2 ---- 這就是閉包的效果 復(fù)制代碼上述代碼中,在foo()執(zhí)行后,其返回值(也就是內(nèi)部的bar()函數(shù))賦值給變量baz并調(diào)用baz(),實(shí)際上只是通過不同的標(biāo)識(shí)符引用調(diào)用了內(nèi)部的函數(shù)bar()。
bar()顯然可以被正常執(zhí)行。但是在這個(gè)例子中,它在自己定義的詞法作用域以外的地方執(zhí)行。
在foo()執(zhí)行后,通常會(huì)期待foo()的整個(gè)內(nèi)部作用域都被銷毀,因?yàn)槲覀冎酪嬗欣厥掌鱽磲尫挪辉偈褂玫膬?nèi)存空間。而閉包的“神奇”之處正是可以阻止這件事情的發(fā)生。事實(shí)上內(nèi)部作用域依然存在,因此沒有被回收,因?yàn)閎ar()本身在使用。
因?yàn)閎ar()所聲明的位置,它擁有涵蓋foo()內(nèi)部作用域的閉包。使得該作用域一直存活,以供bar()在之后任何時(shí)間進(jìn)行引用。
bar()依然持有對(duì)該作用域的引用,而這個(gè)引用就叫作閉包。
閉包使得函數(shù)可以繼續(xù)訪問定義時(shí)的詞法作用域。當(dāng)然,無論使用何種方式對(duì)函數(shù)類型的值進(jìn)行傳遞,當(dāng)函數(shù)在別處被調(diào)用時(shí)都可以觀察到閉包。
function foo(){var a = 2;function baz(){console.log(a);}bar(baz); } function bar(fn){fn(); // 這就是閉包 } 復(fù)制代碼把內(nèi)部函數(shù)baz傳遞給bar,當(dāng)調(diào)用這個(gè)內(nèi)部函數(shù)時(shí)(現(xiàn)在叫作fn),它涵蓋的foo()內(nèi)部作用域的閉包就可以觀察到了,因?yàn)樗軌蛟L問a。
傳遞函數(shù)當(dāng)然也可以是間接的。
var fn ; function foo(){var a = 2;function baz(){console.log(a);}fn = baz; // 將baz分配給全局變量 } function bar(){fn(); // 這就是閉包 } 復(fù)制代碼無論通過何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外,它都會(huì)持有對(duì)原始定義作用域的引用,無論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包。
2.現(xiàn)在我懂了
function wait(message){setTimeout(function timer(){console.log(message);},1000) } wait("Hello,closure!") 復(fù)制代碼將一個(gè)內(nèi)部函數(shù)(名為timer)傳遞給setTimeout(..)。timer具有涵蓋wait(..)作用域的閉包,因此還保有對(duì)變量message的引用。
wait(..)執(zhí)行1000毫秒后,它的內(nèi)部作用域并不會(huì)消失,timer函數(shù)依然保有wait(..)作用域的閉包。
本質(zhì)上,無論何時(shí)何地,如果將(訪問它們各自詞法作用域的)函數(shù)當(dāng)作第一級(jí)的值類型并到處傳遞,你就會(huì)看到閉包在這些函數(shù)中的作用。在定時(shí)器、事件監(jiān)聽器、Ajax請求、跨窗口通信、Web Workers或者任何其他的異步(或者同步)任務(wù)中,只要使用了回調(diào)函數(shù),實(shí)際上就是在使用閉包。
3.循環(huán)和閉包
for(var i = 1;i <= 5; i++){setTimeout(function timer(){console.log(i); // 每秒一次的頻率輸出5次6},i*1000) } 復(fù)制代碼根據(jù)作用域的工作原理,實(shí)際情況是盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的,但是它們都被封閉在一個(gè)共享的全局作用域中,因此實(shí)際上只有一個(gè)i。
for(var i = 1; i <= 5; i++){(function(j){setTimeout(function timer(){console.log(j);},j*1000);})(i) } 復(fù)制代碼在迭代內(nèi)使用IIFE會(huì)為每個(gè)迭代都生成一個(gè)新的作用域,使得延遲函數(shù)的回調(diào)可以將新的作用域封閉在每個(gè)迭代內(nèi)部,每個(gè)迭代中都會(huì)含有一個(gè)具有正確值的變量供我們訪問。
4.重返塊作用域
for(let i = 1; i <= 5; i++){setTimeout(function timer(){console.log(i);},i*1000) } 復(fù)制代碼5.模塊
function foo(){var something = "cool";var another = [1,2,3];function doSomething(){console.log(something);}function doAnother(){console.log(another.join(" ! ")} } 復(fù)制代碼私有數(shù)據(jù)變量something和another,以及doSomething()和doAnother()兩個(gè)內(nèi)部函數(shù),它們的詞法作用域(而這就是閉包)也就是foo()的內(nèi)部作用域。
function CoolModule(){var something = "cool";var another = [1,2,3];function doSomething(){console.log(something);}function doAnother(){console.log(another.join("!");}return {doSomething: doSomething,doAnother: doAnother} }var foo = CoolModule(); foo.doSomething(); // cool foo.doAnother(); // 1!2!3 復(fù)制代碼這個(gè)模式在JavaScript中被稱為模塊。
模塊模式需要具備兩個(gè)必要條件。
1.必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊實(shí)例)。
2.封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)。
一個(gè)從函數(shù)調(diào)用所返回的,只有數(shù)據(jù)屬性而沒有閉包函數(shù)的對(duì)象并不是真正的模塊。
var foo = (function CoolModule(){var something = "cool";var another = [1,2,3];function doSomething(){console.log(something);}function doAnother(){console.log(another.join("!");}return {doSomething: doSomething,doAnother: doAnother} })() foo.doSomething(); // cool foo.doAnother(); // 1!2!3 復(fù)制代碼將模塊函數(shù)轉(zhuǎn)換成了IIFE,立即調(diào)用這個(gè)函數(shù)并將返回值直接賦值給單例的模塊實(shí)例標(biāo)識(shí)符foo。
模塊模式的一個(gè)簡單但強(qiáng)大的用法是命名將要作為公共API返回的對(duì)象:
var foo = (function CoolModule(id){function change(){// 修改公共APIpublicAPI.identify = identify2;}function identify1(){console.log(id);}function identify2(){console.log(id.toUpperCase());}var publicAPI = {change: change,identify: identify1}return publicAPI; }("foo mocule");foo.identify(); // foo module foo.change(); foo.identify(); // FOO MODULE 復(fù)制代碼通過在模塊實(shí)例的內(nèi)部保留對(duì)公共API對(duì)象的內(nèi)部引用,可以從內(nèi)部對(duì)模塊實(shí)例進(jìn)行修改,包括添加或刪除方法和屬性,以及修改它們的值。
總結(jié)
我們在詞法作用域的環(huán)境下寫代碼,而其中的函數(shù)也是值,可以隨意傳來傳去。
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行,這時(shí)就產(chǎn)生了閉包。
閉包是一個(gè)非常強(qiáng)大的工具,可以用多種形式來實(shí)現(xiàn)模塊等模式。
模塊有兩個(gè)主要特征:
- 1.為創(chuàng)建內(nèi)部作用域而調(diào)用了一個(gè)包裝函數(shù)
- 2.包裝函數(shù)的返回值必須至少包括一個(gè)對(duì)內(nèi)部函數(shù)的引用,這樣就會(huì)創(chuàng)建涵蓋整個(gè)包裝函數(shù)內(nèi)部作用域的閉包。
巴拉巴拉
關(guān)于腦子一熱
我的經(jīng)歷告訴我,腦子一熱做的事情,多半會(huì)后悔,而且會(huì)非常后悔。但是怎么去避免呢,方法我還沒找到,每次我遇到這樣的情緒,都會(huì)找各種理由去逃避,這是目前我的低級(jí)應(yīng)對(duì)措施,相當(dāng)?shù)图?jí)。如果能從根源消除是最好不過的了,可是我還沒有那么大的控制力,所以只能慢慢去培養(yǎng),盡量減少這種上頭的次數(shù)了。
轉(zhuǎn)載于:https://juejin.im/post/5d0a363a51882563194b31f2
總結(jié)
以上是生活随笔為你收集整理的《你不知道的JavaScript》-- 精读(五)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python3 -c 和 python3
- 下一篇: gradle idea java ssm