JavaScript的閉包問題

JS的閉包真的是一個老生常談的知識點了狂窑,無奈它并不是那么好掌握,但是它又是那么重要贩耐,很多高級應(yīng)用的開發(fā)都會用到閉包去解決相關(guān)問題耻瑟。希望你能從這篇文章了解基本的閉包知識點旨指,后期會不定期更新使之更加完善捻悯。

在了解閉包知識以前,先說下函數(shù)作用域問題淤毛。在一些類似C語言的編程語言中,它們是具有塊級作用域(block scope)的算柳,但是在JavaScript中卻沒有塊級作用域低淡,取而代之的是函數(shù)作用域(function scope):“變量在聲明它們的函數(shù)體以及這個函數(shù)體嵌套的任意函數(shù)體內(nèi)都是有定義的”。(引用自《JavaScript權(quán)威指南》)

例如如下代碼:

function test(o)?{

var i = 0;????// i在整個函數(shù)體中都是有定義

if(typeof o ==?"object")?{

var j = 0;????// j在函數(shù)體中是有定義的瞬项,不僅僅是在這段代碼內(nèi)

for(var k = 0; k < 10; k++)?{??// k在函數(shù)體內(nèi)是有定義的蔗蹋,不僅僅是在循環(huán)內(nèi)

console.log(k);????//?輸出數(shù)字0-9

}

console.log(k);????// k已經(jīng)定義了,輸出數(shù)字10

}

console.log(j);????// j已經(jīng)定義了囱淋,但是可能沒有初始化

}

上面這段代碼中猪杭,如果我們傳入不同的參數(shù),j的打印結(jié)果是不一樣的妥衣。比如:

var o =?{name:?"qin", old: 23};

test(o);

當(dāng)傳入?yún)?shù)為object時皂吮,會執(zhí)行if判斷語句中的代碼塊,這個時候j的值打印出來為0税手;

var o =?"qin";

test(o);

當(dāng)傳入?yún)?shù)不為object的時候蜂筹,就不會執(zhí)行if判斷語句中的代碼塊,這個時候j的值打印為undefined芦倒。這個時候的變量j就屬于定義了未初始化艺挪。

JavaScript的函數(shù)作用域是指在函數(shù)內(nèi)聲明的所有變量在函數(shù)體內(nèi)始終是可見的。而且JavaScript函數(shù)里聲明的所有變量都會被提前至函數(shù)頂部兵扬,這叫做聲明提前(hoisting)麻裳。

在JavaScript中每一個全局代碼或函數(shù)所包含的代碼塊中,都有一個與之關(guān)聯(lián)的作用域鏈(scope chain)器钟,只有弄懂這個作用域鏈才能更好的去理解閉包問題津坑。

作用域鏈?zhǔn)且粋€對象列表或者鏈表,當(dāng)執(zhí)行JavaScript代碼需要查找變量y的值的時候(這個過程叫做“變量解析”)傲霸,它會從鏈表中的第一個對象開始查找国瓮,如果這個對象中有一個名為y的屬性,則會直接使用這個值狞谱,如果沒有乃摹,就會繼續(xù)查找下一個對象,以此類推跟衅。當(dāng)整個鏈表的對象中都沒有y這個屬性的話孵睬,就會拋出一個引用異常(ReferenceError)的錯誤。

簡單理解就是:子對象會一級一級地向上尋找所有父對象的變量伶跷。所以掰读,父對象的所有變量秘狞,對子對象都是可見的,反之則不成立蹈集。

那么這個作用域鏈表對象創(chuàng)建規(guī)則是怎樣的呢烁试?大致分三種情況:

1.整個代碼不包含任何函數(shù):

這時的作用域鏈對象是由一個全局對象組成。比如:

var n =?"qin";

if(typeof n ==?"string")?{

for(var i = 0; i < 5; i++)?{

console.log(i);

}

}

console.log(i);

上面這段代碼中的作用域鏈為{n:?"qin", i: 5}拢肆。

2.整個代碼包含函數(shù)减响,但不包括嵌套函數(shù):

這時的作用域鏈有兩個對象,第一個是定義函數(shù)參數(shù)和局部變量的對象郭怪,第二個是全局對象支示。比如:

var x =?"qin";

function test()?{

var k = 3;

for(var i = 0; i < 3; i++)?{

k += 1;

}

console.log(k);?????//?打印結(jié)果為6

}

test();

console.log(x);????//?打印結(jié)果為qin

console.log(i);????//?會拋出ReferenceError錯誤

上面這段代碼中存在兩個作用域鏈,第一個是函數(shù)局部變量的作用域鏈{i: 3, k: 6, x:?"qin"}鄙才,第二個是全局對象的作用域鏈{x:?"qin"}颂鸿。當(dāng)我們在函數(shù)外部打印i的值的時候,JavaScript會去全局對象的作用域鏈查找屬性為i的值攒庵,但是全局對象的作用域鏈并不存在i這個屬性嘴纺,因此就會拋出引用異常錯誤(ReferenceError)。

3.代碼中存在函數(shù)浓冒,且有嵌套函數(shù):

這時的作用域鏈至少有三個對象(因嵌套函數(shù)的數(shù)量增加而增加)颖医,第一個是全局對象的作用域鏈,第二個是最外層函數(shù)的參數(shù)和局部變量的作用域鏈裆蒸,第三個是嵌套函數(shù)的參數(shù)和局部變量的作用域鏈熔萧。比如:

var x = 0;

function test()?{

var y = 2;

x += 1;

function foo()?{

var a = 3;

console.log(y);????//?結(jié)果為2

}

foo();

console.log(a);????//?會拋出ReferenceError錯誤

}

test();

console.log(x);????//?結(jié)果為1

上面這段代碼中,函數(shù)test中包含一個嵌套函數(shù)foo,這段代碼含三個作用域鏈僚祷,第一個是全局對象作用域鏈{x: 1}佛致,第二個是函數(shù)test的作用域鏈{y: 2, x: 1},第三個是嵌套函數(shù)foo的作用域鏈{a: 3辙谜,y: 2, x: 1}俺榆。

當(dāng)我們定義一個函數(shù)的時候,其實它就已經(jīng)保存了一個作用域鏈装哆。當(dāng)我們調(diào)用這個函數(shù)罐脊,它會創(chuàng)建新的對象來存儲它的局部變量,并將這個對象添加到保存的那個作用域鏈上蜕琴,同時還會創(chuàng)建一個新的更長的表示函數(shù)調(diào)用作用域的“鏈”萍桌。對于嵌套函數(shù)來說,每次調(diào)用外部函數(shù)的時候凌简,內(nèi)部函數(shù)又會重新定義一遍上炎。因為每次調(diào)用外部函數(shù)的時候,作用域鏈都是不同的雏搂。內(nèi)部函數(shù)在每次定義的時候都會有微妙的差別——在每次調(diào)用外部函數(shù)時藕施,內(nèi)函數(shù)的代碼都是相同的寇损,而且關(guān)聯(lián)這段代碼的作用域鏈也不相同。(引用自《JavaScript權(quán)威指南》)

關(guān)于變量作用域及函數(shù)作用域可參考這篇文章:什么是變量作用域和函數(shù)作用域裳食?(坑未填)

說完函數(shù)作用域和作用域鏈矛市,接下來就要開始理解什么是閉包了。

首先在上面包含嵌套函數(shù)的例子中诲祸,我們?nèi)绾卧谕鈱雍瘮?shù)test中訪問到嵌套函數(shù)foo中的變量a的值呢浊吏?

其實很簡單,我們只需要把foo中a變量返回就可以了烦绳,如下:

var x = 0;

function test()?{

var y = 2;

x += 1;

function foo()?{

var a = 3;

console.log(y);????//?結(jié)果為2

return a;

}

var res = foo();

console.log(res);//?結(jié)果為3

}

test();

console.log(x);????//?結(jié)果為1

其實上面的代碼就是典型的閉包,閉包函數(shù)為foo配紫。

個人理解閉包的概念就是:

有權(quán)訪問另一個函數(shù)作用域內(nèi)變量的函數(shù)就是閉包径密。

本質(zhì)上閉包就是將函數(shù)內(nèi)部與外部聯(lián)系起來的一座橋梁。

閉包的用途:

閉包的最大用處有兩個:

第一是上面所說的可以讀取到函數(shù)內(nèi)部的變量躺孝。

第二則是讓這些變量值能一直保存在內(nèi)存中享扔。

來看一個比較有趣的例子:

function makeAdder(x)?{

return function(y)?{

return x + y;

};

}

var add5 = makeAdder(5);

var add10 = makeAdder(10);

console.log(add5(2));?????// 7

console.log(add10(2));???// 12

上面的例子中,我們定義了函數(shù)makeAdder植袍,它接受一個參數(shù)x惧眠,并返回一個新的函數(shù)。它返回的函數(shù)使用一個參數(shù)y于个,并返回x+y的值氛魁。

add5和add10都是兩個閉包函數(shù),我們?yōu)樗麄儌魅氩煌膮?shù)x厅篓,一個為5秀存,一個為10。實際上這兩個函數(shù)有著各自獨立的作用域鏈羽氮,并不相互影響或链。

使用閉包應(yīng)該注意的點:

1.由于閉包會使得函數(shù)中的變量都會被保存在內(nèi)存中,內(nèi)存消耗很大档押,因此不能濫用閉包澳盐,否則會導(dǎo)致網(wǎng)頁性能問題,在IE中還可能造成內(nèi)存泄漏令宿。解決辦法是在退出函數(shù)之前叼耙,將不使用的局部變量全部刪除。

2.閉包會在父函數(shù)外部改變父函數(shù)內(nèi)部的值粒没,所以你在把父函數(shù)當(dāng)做對象使用旬蟋,把閉包當(dāng)做它的公用方法,把內(nèi)部變量當(dāng)做它的私有屬性的時候革娄,一定要小心不要隨便修改父函數(shù)內(nèi)部變量的值倾贰。

最后放兩個思考題:

思考題一:

var name =?"The Window";

var object =?{

name :?"My Object",

getNameFunc : function(){

return function(){

return this.name;

};

}

};

alert(object.getNameFunc()());????//?打印出什么冕碟?

思考題二:

var name =?"The Window";

var object =?{

name :?"My Object",

getNameFunc : function(){

var that = this;

return function(){

return that.name;

};

}

};

alert(object.getNameFunc()());????//?打印出什么?

參考文獻及資料:

《JavaScript權(quán)威指南》

阮一峰博客《學(xué)習(xí)JavaScript閉包(closure)》

如果你在本文中發(fā)現(xiàn)錯誤或者有異議的地方匆浙,可以在評論留言安寺,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末首尼,一起剝皮案震驚了整個濱河市挑庶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌软能,老刑警劉巖迎捺,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異查排,居然都是意外死亡凳枝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門跋核,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岖瑰,“玉大人,你說我怎么就攤上這事砂代√6” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵刻伊,是天一觀的道長露戒。 經(jīng)常有香客問我,道長捶箱,這世上最難降的妖魔是什么玫锋? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮讼呢,結(jié)果婚禮上撩鹿,老公的妹妹穿的比我還像新娘。我一直安慰自己悦屏,他們只是感情好节沦,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著础爬,像睡著了一般甫贯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上看蚜,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天叫搁,我揣著相機與錄音,去河邊找鬼。 笑死渴逻,一個胖子當(dāng)著我的面吹牛疾党,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惨奕,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼雪位,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梨撞?” 一聲冷哼從身側(cè)響起雹洗,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卧波,沒想到半個月后时肿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡港粱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年螃成,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啥容。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡锈颗,死狀恐怖顷霹,靈堂內(nèi)的尸體忽然破棺而出咪惠,到底是詐尸還是另有隱情,我是刑警寧澤淋淀,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布遥昧,位于F島的核電站,受9級特大地震影響朵纷,放射性物質(zhì)發(fā)生泄漏炭臭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一袍辞、第九天 我趴在偏房一處隱蔽的房頂上張望鞋仍。 院中可真熱鬧,春花似錦搅吁、人聲如沸威创。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肚豺。三九已至,卻和暖如春界拦,著一層夾襖步出監(jiān)牢的瞬間吸申,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留截碴,地道東北人梳侨。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像隐岛,于是被迫代替她去往敵國和親猫妙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

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

  • 原文:http://dmitrysoshnikov.com/ecmascript/javascript-the-c...
    jaysoul閱讀 473評論 0 0
  • ● 閉包基礎(chǔ) ● 閉包作用 ● 閉包經(jīng)典例子 ● 閉包應(yīng)用 ● 閉包缺點 ● 參考資料 1聚凹、閉包基礎(chǔ) 作用域和作...
    lzyuan閱讀 921評論 0 0
  • 轉(zhuǎn)載請著名出處 GitHub-TYRMars 文章Github地址 JavaScript基礎(chǔ)知識剖析 01 01-...
    TYRMars閱讀 551評論 0 7
  • 1.對象是什么 對象就是若干屬性的集合割坠。 在JS中一切引用類型都是對象:數(shù)組是對象,函數(shù)是對象妒牙,對象還是對象彼哼。對象...
    liushaung閱讀 1,205評論 0 2
  • 好燃 這是看電影的時候腦海中出現(xiàn)的一個詞 最適合形容這部電影的一個詞 當(dāng)影片出現(xiàn)了中國大使館深夜將華僑送回國內(nèi)的、...
    CCHanY閱讀 265評論 0 0