解釋JavaScript中的閉包

去年我寫了一篇“closures的簡介”,它的目的是幫助大家理解‘什么是閉包,閉包是如何工作的’〗寥梗現(xiàn)在我嘗試從另外一個不同的角度去闡釋閉包。有了這些基本的概念裹芝,你只需要盡可能多地閱讀這些解釋部逮,來更全面地理解閉包。

First-class functions

就像我在“Why JavaScript is AWESOME”中解釋的那樣嫂易,JavaScript的強大之處的一部分來自于它的’first-class functions‘甥啄。那么編程語言中的’first-class‘意味著什么?

可以被存放在變量和數(shù)據(jù)結(jié)構(gòu)中

可以作為子例程的參數(shù)被傳遞

可以作為子例程的返回值被返回

可以在運行時被構(gòu)造

有固有的id(區(qū)別于任何給定的名字)

所以炬搭,JavaScript中的functions非常類似objects蜈漓。事實上,在JavaScript中functions就是objects宫盔。能夠嵌套使用函數(shù)融虽,讓我們可以使用閉包,這也是我接下來要討論的...

Nested functions(嵌套函數(shù))

如下是嵌套函數(shù)的一個小例子:

http://jsfiddle.net/skilldrick/66jFm/embedded/

在這兒要注意的重要事情是只有唯一的一個f函數(shù)被定義灼芭。每次函數(shù)f被調(diào)用后有额,一個新的函數(shù)g被創(chuàng)建,(函數(shù)g)局部于函數(shù)f的執(zhí)行過程中彼绷。當函數(shù)g被返回時巍佑,我們可以把它賦值給一個全局變量。所以寄悯,我們可以調(diào)用f函數(shù)萤衰,把結(jié)果賦值給變量g5;接著我們再一次調(diào)用f函數(shù)猜旬,并把結(jié)果賦值給變量g1脆栋。g1和g5是2個不同的函數(shù)倦卖,但碰巧的是它們共享著同一份代碼,只不過它們他們在不同的上下文中被執(zhí)行椿争,使用著不同的‘free variable(自由變量)’怕膛。(題外話,使用函數(shù)定義去定義‘函數(shù)g’秦踪,接著返回函數(shù)g褐捻,并不是必需的。我們可以使用函數(shù)表達式作為替代椅邓,函數(shù)表達式允許我們創(chuàng)建函數(shù)時不用命名函數(shù)柠逞。也就是直接返回匿名函數(shù)。這兒是使用匿名函數(shù)替換后的版本)

Free variables and scope(自由變量和作用域)

如果一個變量在包含它的作用域中被定義希坚,那么該變量在包含它的作用域內(nèi)的任何其它作用域內(nèi)都是自由的(原文:A variable is free in any particular scope if it is defined within an enclosing scope.)。為了表達的更具體陵且,也就是:在函數(shù)g的作用域內(nèi)裁僧,變量x是自由的。因為變量x是在函數(shù)f的作用域內(nèi)被定義的(而且‘作用域f’包含‘作用域g’)慕购。同樣地聊疲,任何全局變量在‘作用域f’和’作用域g‘內(nèi)也是自由的。

作用域意味著什么沪悲?一個作用域是一個代碼區(qū)获洲,在該代碼區(qū)中可以定義變量,并且包圍該作用域的外圍作用域不能訪問該作用域內(nèi)的變量(原文:A scope is an area of code where a variable may be defined, without the enclosing scope knowing about it.)殿如。JavaScript有‘函數(shù)作用域’贡珊,所以函數(shù)有它自己的作用域。所以在‘函數(shù)f’中定義的任何變量涉馁,外部都是看不到的门岔。作用域是可以嵌套的,所以烤送,在上述例子中寒随,函數(shù)g有它自己的作用域,函數(shù)g的作用域被函數(shù)f包圍著帮坚,函數(shù)f的作用域被全局作用域包圍著妻往。當一個變量被訪問時,JavaScript解釋器在當前作用域內(nèi)查找變量试和,如果在當前作用域內(nèi)找不到該變量的定義讯泣,解釋器會查看包圍著當前作用域的作用域,接著是查看爺爺作用域阅悍,一直向上直到全局作用域判帮。如果此時局嘁,變量的定義仍未被找到,一個ReferenceError異常就會被拋出晦墙。

Closures are functions that retain a reference to their free variables(閉包是‘保留著它們的自由變量的一份引用’的函數(shù))

并且這是問題的本質(zhì)悦昵。讓我們先看下上述例子的一個簡化版本:

http://jsfiddle.net/skilldrick/XDrsn/embedded/

調(diào)用函數(shù)f時傳遞一個參數(shù)5,執(zhí)行函數(shù)f時晌畅,函數(shù)g會被調(diào)用但指。當函數(shù)g被調(diào)用時,函數(shù)g可以訪問那個形參x抗楔,這并沒有什么奇怪的棋凳。令人驚訝的地方在于,當你從函數(shù)f中返回函數(shù)g后连躏,返回的函數(shù)g在被調(diào)用時仍然可以訪問你傳遞的參數(shù)5(就像原先那個例子中展示的那樣)剩岳。讓人迷惑的地方在于:函數(shù)g被返回后,仍然記得在函數(shù)f被調(diào)用時被定義的變量x(這也是大家理解閉包時入热,有困惑的地方)拍棕。從這點來說,確實不能理解勺良。那么看看下面的例子:

http://jsfiddle.net/skilldrick/DEUdF/embedded/

當person被調(diào)用绰播,形參name一定是被傳遞的值。所以尚困,person第一次被調(diào)用時蠢箩,name一定是‘Dave’,person第二次被調(diào)用事甜,name一定是‘Mary’谬泌。person定義了2個內(nèi)部函數(shù)‘set和get’。當這些函數(shù)(set和get)第一次被定義時逻谦,它們有一個‘自由變量name’呵萨,并且name的值一定是’Dave‘。這2個函數(shù)被數(shù)組包裹著返回跨跨,在外部被取出并賦值給2個變量’getDave和setDave‘潮峦。(如果你想從函數(shù)中返回一個以上的值,你要么返回一個對象勇婴,要么返回一個數(shù)組忱嘹。在這里使用數(shù)組顯得有點啰嗦,但是如果使用對象的話會混淆我們討論的問題耕渴。這兒又一個使用對象的版本拘悦,如果它對你來說更好理解的話)

并且這就是神奇的地方,getDave和setDave都記得同一個變量name(name初始時一定是Dave)橱脸。當setDave(’Bob‘)被調(diào)用時础米,變量name被設(shè)置為’Bob‘》治現(xiàn)在getDave被調(diào)用,它返回了’Bob‘屁桑。所以getDave和setDave這兩個函數(shù)記得同一個變量医寿。這也就是我想表達的含義:’閉包是保留它們自由變量的一份引用的函數(shù)‘。getDave和setDave都記得它們共有的自由變量name蘑斧。即使person已經(jīng)返回靖秩,但是變量name繼續(xù)存活,因為變量name被getDave和setDave引用著竖瘾。

當person第一次被調(diào)用時沟突,變量name一定是’Dave‘。當person第二次被調(diào)用時捕传,變量name的一份新版本被創(chuàng)建惠拭,當然get和set也被新建了一份。所以getMary庸论,setMary函數(shù)和getDave职辅,setDave是完全不同的。雖然它們執(zhí)行著同樣的代碼葡公,但是它們的上下文環(huán)境不同罐农,有著不同的自由變量条霜。

Summary總結(jié)

總的來說催什,閉包是一個函數(shù)’該函數(shù)在一個上下文中被調(diào)用,(該函數(shù))卻記得在另一個上下文中定義的變量‘(也就是該函數(shù)被定義的上下文)宰睡。在同一個上下文中定義的多個閉包記得同樣的上下文蒲凶,所以任何一個閉包修改上下文,其他閉包也會受影響(因為多個閉包共享同一個上下文拆内,就像上面例子顯示的那樣 setDave('Bob')后 getDave()也會受到影響)旋圆。









本文翻譯自:http://skilldrick.co.uk/2011/04/closures-explained-with-javascript/








轉(zhuǎn)載請注明出處








最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市麸恍,隨后出現(xiàn)的幾起案子灵巧,更是在濱河造成了極大的恐慌,老刑警劉巖抹沪,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刻肄,死亡現(xiàn)場離奇詭異,居然都是意外死亡融欧,警方通過查閱死者的電腦和手機敏弃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來噪馏,“玉大人麦到,你說我怎么就攤上這事绿饵。” “怎么了瓶颠?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵拟赊,是天一觀的道長。 經(jīng)常有香客問我步清,道長要门,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任廓啊,我火速辦了婚禮欢搜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谴轮。我一直安慰自己炒瘟,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布第步。 她就那樣靜靜地躺著疮装,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粘都。 梳的紋絲不亂的頭發(fā)上廓推,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音翩隧,去河邊找鬼樊展。 笑死,一個胖子當著我的面吹牛堆生,可吹牛的內(nèi)容都是我干的专缠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼淑仆,長吁一口氣:“原來是場噩夢啊……” “哼涝婉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蔗怠,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤墩弯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后寞射,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渔工,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年怠惶,在試婚紗的時候發(fā)現(xiàn)自己被綠了涨缚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脓魏,靈堂內(nèi)的尸體忽然破棺而出兰吟,到底是詐尸還是另有隱情,我是刑警寧澤茂翔,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布混蔼,位于F島的核電站,受9級特大地震影響珊燎,放射性物質(zhì)發(fā)生泄漏惭嚣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一悔政、第九天 我趴在偏房一處隱蔽的房頂上張望晚吞。 院中可真熱鬧,春花似錦谋国、人聲如沸槽地。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捌蚊。三九已至,卻和暖如春近弟,著一層夾襖步出監(jiān)牢的瞬間缅糟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工祷愉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窗宦,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓谣辞,卻偏偏與公主長得像迫摔,于是被迫代替她去往敵國和親沐扳。 傳聞我的和親對象是個殘疾皇子泥从,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容