對(duì)于JavaScript新手來說瞬欧,閉包(Closures)是一個(gè)很神奇的東西。這篇講解將通過一個(gè)非常淺顯的代碼示例來解釋閉包阅羹。
計(jì)數(shù)器
我們的目標(biāo)是實(shí)現(xiàn)一個(gè)計(jì)數(shù)器为居,它的效果如下:
可知,每次執(zhí)行increment()都會(huì)輸出"Number of events: N"忌怎,且N每次都會(huì)加1籍滴。
這個(gè)計(jì)數(shù)器最直觀的實(shí)現(xiàn)方式如下:
多個(gè)計(jì)數(shù)器
以上的代碼非常簡單。但是榴啸,當(dāng)我們需要第二個(gè)計(jì)數(shù)器時(shí)孽惰,就會(huì)遇到問題了。當(dāng)然鸥印,我們可以實(shí)現(xiàn)兩個(gè)重復(fù)的計(jì)數(shù)器:
顯然勋功,以上的代碼非常冗余,有待優(yōu)化库说。當(dāng)我們需要更多計(jì)數(shù)器時(shí)狂鞋,使用這種方法將不太現(xiàn)實(shí)。這時(shí)潜的,就需要神奇的閉包了骚揍。
使用閉包實(shí)現(xiàn)計(jì)數(shù)器
需要多個(gè)計(jì)數(shù)器,同時(shí)希望去除冗余代碼的話啰挪,就可以使用閉包了:
在代碼中信不,我們創(chuàng)建了兩個(gè)獨(dú)立的計(jì)數(shù)器counter1與counter2纤掸,分別進(jìn)行計(jì)數(shù),互不干撓浑塞。代碼看著有點(diǎn)奇怪借跪,我們不妨拆分起來分析。
首先酌壕,我們來看看createCounter:
創(chuàng)建了一個(gè)局部變量counter
創(chuàng)建了一個(gè)局部函數(shù)increment()掏愁,它可以對(duì)counter變量進(jìn)行加1操作。
將局部函數(shù)increment()返回卵牍。注意果港,返回的是函數(shù)本身,而不是函數(shù)調(diào)用的結(jié)果糊昙。
看起來辛掠,createCounter()函數(shù)與我們最初定義的計(jì)數(shù)器非常相似。唯一的不同點(diǎn)在于:createCounter()將計(jì)數(shù)器封裝在一個(gè)函數(shù)內(nèi)释牺,于是我們將它稱作閉包萝衩。
難以理解的一點(diǎn)在于,當(dāng)我們使用createCounter()函數(shù)創(chuàng)建計(jì)數(shù)器時(shí)没咙,實(shí)際上創(chuàng)建了一個(gè)新的函數(shù):
閉包的神奇之處在于猩谊。每次使用createCounter()函數(shù)創(chuàng)建計(jì)數(shù)器increment時(shí),都會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的counter變量祭刚。并且牌捷,返回的increment函數(shù)會(huì)始終記住counter變量。
更重要的是涡驮,這個(gè)counter變量是相互獨(dú)立的暗甥。比如,當(dāng)我們創(chuàng)建2個(gè)計(jì)數(shù)器時(shí)捉捅,每個(gè)計(jì)數(shù)器都會(huì)創(chuàng)建一個(gè)新的counter變量:
為計(jì)數(shù)器命名
多個(gè)計(jì)數(shù)器的輸出信息都是“Number of events: N”撤防,這樣容易混淆。如果可以為每個(gè)計(jì)數(shù)器命名锯梁,則更加方便:
通過給createCounter傳遞一個(gè)新的counterName參數(shù)即碗,可以很容易地做到這一點(diǎn):
這樣焰情,createCounter()函數(shù)返回的計(jì)數(shù)器將同時(shí)記住兩個(gè)局部變量:counterName與counter陌凳。
優(yōu)化計(jì)數(shù)器調(diào)用方式
按照之前的實(shí)現(xiàn)方式,我們通過調(diào)用createCounter()函數(shù)可以返回一個(gè)計(jì)數(shù)器内舟,直接調(diào)用返回的計(jì)數(shù)器就可以加1合敦,這樣做并不直觀。如果可以如下調(diào)用將更好:
實(shí)現(xiàn)代碼:
可看出验游,以上的代碼返回了一個(gè)對(duì)象充岛,這個(gè)對(duì)象包含了一個(gè)increment方法保檐。
添加decrement方法
現(xiàn)在,我們可以給計(jì)數(shù)器添加一個(gè)decrement()方法
添加私有方法
前面的代碼有兩行重復(fù)的代碼崔梗,即console.log語句夜只。因此,我們可以創(chuàng)建一個(gè)display()方法用于打印counter的值:
看起來蒜魄,display()函數(shù)與increment()函數(shù)以及decrement()函數(shù)差不多扔亥,但是其實(shí)它們很不一樣。我們并沒有將display()函數(shù)添加到返回的對(duì)象中谈为,這就意味著以下代碼會(huì)出錯(cuò):
這時(shí)旅挤,display()相當(dāng)于一個(gè)私有方法,我們只能在createCounter()函數(shù)內(nèi)使用它伞鲫。
閉包與面向?qū)ο缶幊?/b>
如果你接觸過面向?qū)ο缶幊?OOP)粘茄,則應(yīng)該不難發(fā)現(xiàn)本文中所涉及的內(nèi)容與OOP中的類、對(duì)象秕脓、對(duì)象屬性柒瓣、共有方法與私有方法等概念非常相似。
閉包吠架,與OOP相似嘹朗,就是把數(shù)據(jù)和操作數(shù)據(jù)的方法綁定起來。因此诵肛,在需要OOP的時(shí)候屹培,就可以使用閉包來實(shí)現(xiàn)。
總結(jié)
閉包(Closure)是JavaScript一個(gè)非常棒的特性怔檩。掌握它褪秀,我們可以從容應(yīng)對(duì)一些常見的編程需求。