js閉包

一素邪、變量的作用域

要懂得閉包妻顶,起首必須懂得Javascript特別的變量作用域捆等。

變量的作用域無非就是兩種:全局變量和局部變量氓皱。

Javascript說話的特別之處路召,就在于函數(shù)內(nèi)部可以直接讀取全局變量勃刨。

Js代碼

···

var n=999;

function f1(){

alert(n);

}

f1(); // 999

···

另一方面,在函數(shù)外部天然無法讀取函數(shù)內(nèi)的局部變量股淡。

Js代碼

function f1(){

var n=999;

}

alert(n); // error

這里有一個(gè)處所須要重視身隐,函數(shù)內(nèi)部聲明變量的時(shí)候,必然要用var唯灵。若是不用的話贾铝,你實(shí)際上聲明了一個(gè)全局變量!

Js代碼

function f1(){

n=999;

}

f1();

alert(n); // 999

--------------------------------------------------------------------------------------------------------

二埠帕、如何從外部讀取局部變量垢揩?

出于各種原因,我們有時(shí)要獲得函數(shù)內(nèi)的局部變量。然則,前面已經(jīng)說過了昌罩,正常情況下,這是辦不到的俘种,只有經(jīng)由過程變通才能實(shí)現(xiàn)。

那就是在函數(shù)的內(nèi)部绝淡,再定義一個(gè)函數(shù)宙刘。

Js代碼

function f1(){

n=999;

function f2(){

alert(n); // 999

}

}

在上方的代碼中,函數(shù)f2就被包含在函數(shù)f1內(nèi)部牢酵,這時(shí)f1內(nèi)部的所有局部變量悬包,對(duì)f2都是可見的。然則反過來就不可馍乙,f2內(nèi)部的局部變量布近,對(duì)f1 就是不成見的。這就是Javascript說話特有的“鏈?zhǔn)阶饔糜颉辈季郑╟hain scope)丝格,

子對(duì)象會(huì)一級(jí)一級(jí)地向上尋找所有父對(duì)象的變量撑瞧。所以,父對(duì)象的所有變量显蝌,對(duì)子對(duì)象都是可見的预伺,反之則不成立。

既然f2可以讀取f1中的局部變量曼尊,那么只要把f2作為返回值酬诀,我們不就可以在f1外部讀取它的內(nèi)部變量了嗎!

Js代碼

function f1(){

n=999;

function f2(){

alert(n);

}

return f2;

}

var result=f1();

result(); // 999

--------------------------------------------------------------------------------------------------------

三骆撇、閉包的概念

上一節(jié)代碼中的f2函數(shù)瞒御,就是閉包。

各類專業(yè)文獻(xiàn)上的“閉包”(closure)定義很是抽象艾船,很丟臉懂葵腹。我的懂得是高每,閉包就是可以或許讀取其他函數(shù)內(nèi)部變量的函數(shù)屿岂。

因?yàn)樵贘avascript說話中践宴,只有函數(shù)內(nèi)部的子函數(shù)才干讀取局部變量,是以可以把閉包簡(jiǎn)單懂得成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”爷怀。

所以阻肩,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁运授。

--------------------------------------------------------------------------------------------------------b

四烤惊、閉包的用處

閉包可以用在很多處所。它的最大用處有兩個(gè)吁朦,一個(gè)是前面提到的可以讀取函數(shù)內(nèi)部的變量柒室,另一個(gè)就是讓這些變量的值始終對(duì)峙在內(nèi)存中。

怎么來懂得這句話呢逗宜?請(qǐng)看下面的代碼雄右。

Js代碼

function f1(){

var n=999;

nAdd=function(){n+=1}

function f2(){

alert(n);

}

return f2;

}

var result=f1();

result(); // 999

nAdd();

result(); // 1000

在這段代碼中,result實(shí)際上就是閉包f2函數(shù)纺讲。它一共運(yùn)行了兩次擂仍,第一次的值是999,第二次的值是1000熬甚。這就說明逢渔,函數(shù)f1中的局部變量n一向保存在內(nèi)存中,并沒有在f1調(diào)用后被主動(dòng)清除乡括。

為什么會(huì)這樣呢肃廓?原因就在于f1是f2的父函數(shù),而f2被賦給了一個(gè)全局變量诲泌,這導(dǎo)致f2始終在內(nèi)存中盲赊,而f2的存在依附于f1,是以f1也始終在內(nèi)存中档礁,不會(huì)在調(diào)用停止后角钩,被垃圾收受接管機(jī)制(garbage collection)收受接管。

這段代碼中另一個(gè)值得重視的一處呻澜,就是“nAdd=function(){n+=1}”這一行递礼,起首在nAdd前面沒有應(yīng)用var關(guān)鍵字,是以 nAdd是一個(gè)全局變量羹幸,而不是局部變量脊髓。其次,nAdd的值是一個(gè)匿名函數(shù)(anonymous function)栅受,而這個(gè)

匿名函數(shù)本身也是一個(gè)閉包将硝,所以nAdd相當(dāng)于是一個(gè)setter恭朗,可以在函數(shù)外部對(duì)函數(shù)內(nèi)部的局部變量進(jìn)行操縱。

--------------------------------------------------------------------------------------------------------

五依疼、應(yīng)用閉包的重視點(diǎn)

1)因?yàn)殚]包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中痰腮,內(nèi)存消費(fèi)很大,所以不要濫用閉包律罢,不然會(huì)造成網(wǎng)頁的性能題目膀值,在IE中可能導(dǎo)致內(nèi)存泄漏。解決辦法是误辑,在退出函數(shù)之前沧踏,將不應(yīng)用的局部變量全部刪除。

2)閉包會(huì)在父函數(shù)外部巾钉,改變父函數(shù)內(nèi)部變量的值翘狱。所以,若是你把父函數(shù)當(dāng)做難象(object)應(yīng)用砰苍,把閉包算作它的公用辦法(Public Method)潦匈,把內(nèi)部變量算作它的私有屬性(private value),這時(shí)必然要警惕师骗,不要隨便

改變父函數(shù)內(nèi)部變量的值历等。

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

什么是閉包辟癌?

先看一段代碼:

function a(){

var n = 0;

function inc() {

n++;

console.log(n);

}

inc();

inc();

}

a(); //控制臺(tái)輸出1寒屯,再輸出2

簡(jiǎn)單吧。再來看一段代碼:

function a(){

var n = 0;

this.inc = function () {

n++;

console.log(n);

};

}

var c = new a();

c.inc(); //控制臺(tái)輸出1

c.inc(); //控制臺(tái)輸出2

簡(jiǎn)單吧黍少。

什么是閉包寡夹?這就是閉包!

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

這里 inc 函數(shù)訪問了構(gòu)造函數(shù) a 里面的變量 n菩掏,所以形成了一個(gè)閉包。

再來看一段代碼:

function a(){

var n = 0;

function inc(){

n++;

console.log(n);

}

return inc;

}

var c = a();

c(); //控制臺(tái)輸出1

c(); //控制臺(tái)輸出2

看看是怎么執(zhí)行的:

var c = couter()昵济,這一句 couter()返回的是函數(shù) inc智绸,那這句等同于 var c = inc;

c(),這一句等同于 inc(); 注意访忿,函數(shù)名只是一個(gè)標(biāo)識(shí)(指向函數(shù)的指針)瞧栗,而()才是執(zhí)行函數(shù)。

后面三句翻譯過來就是: var c = inc; inc(); inc();海铆,跟第一段代碼有區(qū)別嗎迹恐? 沒有。

什么是閉包卧斟?這就是閉包殴边!

所有的教科書教程上都喜歡用最后一段來說明閉包憎茂,但我覺得這將問題復(fù)雜化了。這里面返回的是函數(shù)名锤岸,沒看過譚浩強(qiáng)C/C++程序設(shè)計(jì)的同學(xué)可能一下子沒反應(yīng)出帶不帶()的區(qū)別竖幔,也就是說這種寫法自帶一個(gè)陷阱。雖然這種寫法更顯高大上能耻,但我還是喜歡將問題單一化赏枚,看看代碼 1 和代碼 2亡驰,你還會(huì)糾結(jié)函數(shù)的調(diào)用晓猛,你會(huì)糾結(jié) n 的值嗎?

為啥要這樣寫凡辱?

我們知道戒职,js的每個(gè)函數(shù)都是一個(gè)個(gè)小黑屋,它可以獲取外界信息透乾,但是外界卻無法直接看到里面的內(nèi)容洪燥。將變量 n 放進(jìn)小黑屋里,除了 inc 函數(shù)之外乳乌,沒有其他辦法能接觸到變量 n捧韵,而且在函數(shù) a 外定義同名的變量 n 也是互不影響的,這就是所謂的增強(qiáng)“封裝性”汉操。

而之所以要用 return 返回函數(shù)標(biāo)識(shí) inc再来,是因?yàn)樵?a 函數(shù)外部無法直接調(diào)用 inc 函數(shù),所以 return inc 與外部聯(lián)系起來磷瘤,代碼 2 中的 this 也是將 inc 與外部聯(lián)系起來而已芒篷。

常見的陷阱

看看這個(gè):

function createFunctions(){

var result = new Array();

for (var i=0; i < 10; i++){

result[i] = function(){

return i;

};

}

return result;

}

var funcs = createFunctions();

for (var i=0; i < funcs.length; i++){

console.log(funcsi);

}

乍一看,以為輸出 0~9 采缚,萬萬沒想到輸出10個(gè)10针炉?

這里的陷阱就是:函數(shù)帶()才是執(zhí)行函數(shù)! 單純的一句 var f = function() { alert(‘Hi’); }; 是不會(huì)彈窗的扳抽,后面接一句 f(); 才會(huì)執(zhí)行函數(shù)內(nèi)部的代碼篡帕。上面代碼翻譯一下就是:

var result = new Array(), i;

result[0] = function(){ return i; }; //沒執(zhí)行函數(shù),函數(shù)內(nèi)部不變贸呢,不能將函數(shù)內(nèi)的i替換镰烧!

result[1] = function(){ return i; }; //沒執(zhí)行函數(shù),函數(shù)內(nèi)部不變贮尉,不能將函數(shù)內(nèi)的i替換拌滋!

...

result[9] = function(){ return i; }; //沒執(zhí)行函數(shù),函數(shù)內(nèi)部不變猜谚,不能將函數(shù)內(nèi)的i替換败砂!

i = 10;

funcs = result;

result = null;

console.log(i); // funcs0就是執(zhí)行 return i 語句赌渣,就是返回10

console.log(i); // funcs1就是執(zhí)行 return i 語句,就是返回10

...

console.log(i); // funcs9就是執(zhí)行 return i 語句昌犹,就是返回10

為什么只垃圾回收了 result坚芜,但卻不收了 i 呢? 因?yàn)?i 還在被 function 引用著啊斜姥。好比一個(gè)餐廳鸿竖,盤子總是有限的,所以服務(wù)員會(huì)去巡臺(tái)回收空盤子铸敏,但還裝著菜的盤子他怎么敢收缚忧? 當(dāng)然,你自己手動(dòng)倒掉了盤子里面的菜(=null)杈笔,那盤子就會(huì)被收走了闪水,這就是所謂的內(nèi)存回收機(jī)制。

至于 i 的值怎么還能保留蒙具,其實(shí)從文章開頭一路讀下來球榆,這應(yīng)該沒有什么可以糾結(jié)的地方。盤子里面的菜禁筏,吃了一塊不就應(yīng)該少一塊嗎持钉?

總結(jié)一下

閉包就是一個(gè)函數(shù)引用另外一個(gè)函數(shù)的變量,因?yàn)樽兞勘灰弥圆粫?huì)被回收篱昔,因此可以用來封裝一個(gè)私有變量每强。這是優(yōu)點(diǎn)也是缺點(diǎn),不必要的閉包只會(huì)徒增內(nèi)存消耗旱爆!另外使用閉包也要注意變量的值是否符合你的要求舀射,因?yàn)樗拖褚粋€(gè)靜態(tài)私有變量一樣。閉包通常會(huì)跟很多東西混搭起來怀伦,接觸多了才能加深理解脆烟,這里只是開個(gè)頭說說基礎(chǔ)性的東西。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末房待,一起剝皮案震驚了整個(gè)濱河市邢羔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桑孩,老刑警劉巖拜鹤,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異流椒,居然都是意外死亡敏簿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惯裕,“玉大人温数,你說我怎么就攤上這事◎呤疲” “怎么了撑刺?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)握玛。 經(jīng)常有香客問我够傍,道長(zhǎng),這世上最難降的妖魔是什么挠铲? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任冕屯,我火速辦了婚禮,結(jié)果婚禮上市殷,老公的妹妹穿的比我還像新娘愕撰。我一直安慰自己,他們只是感情好醋寝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著带迟,像睡著了一般音羞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仓犬,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天嗅绰,我揣著相機(jī)與錄音,去河邊找鬼搀继。 笑死窘面,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叽躯。 我是一名探鬼主播财边,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼点骑!你這毒婦竟也來了酣难?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤黑滴,失蹤者是張志新(化名)和其女友劉穎憨募,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袁辈,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菜谣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尾膊。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡甘磨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出眯停,到底是詐尸還是另有隱情济舆,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布莺债,位于F島的核電站滋觉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏齐邦。R本人自食惡果不足惜椎侠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望措拇。 院中可真熱鬧我纪,春花似錦、人聲如沸丐吓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽券犁。三九已至术健,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粘衬,已是汗流浹背荞估。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稚新,地道東北人勘伺。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像褂删,于是被迫代替她去往敵國(guó)和親飞醉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355