廢話
閉包,這個詞甚至讓很多人看到都覺得頭疼,有的人說這東西非常好用,有人說不懂是啥東西...
引用一段JavaScript權(quán)威指南上一段話:
和其他大多數(shù)現(xiàn)代編程語言一樣,JavaScript也采用詞法作用域(lexical scoping).也就是,函數(shù)的執(zhí)行依賴于變量作用域,這個作用域是函數(shù)定義時決定的.而不是函數(shù)執(zhí)行時決定的.為了實現(xiàn)這種詞法作用域,JavaScript函數(shù)對象的內(nèi)部狀態(tài)不僅包含函數(shù)的代碼邏輯,還必須引用當前的作用域鏈(scope chain).函數(shù)可以通過作用域鏈(scope chain)相互關(guān)聯(lián)起來,函數(shù)內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),這種特性稱為"閉包”.從某種角度上來說,JavaScript中所有的函數(shù)都是閉包:他們都是對象,他們都關(guān)聯(lián)到作用域鏈.
這段話,很多人即使讀了幾遍以后還是云里霧里,其實關(guān)于這段話,你只需要記住一句:
函數(shù)的執(zhí)行依賴于變量作用域,這個作用域是函數(shù)定義時決定的,而不是函數(shù)執(zhí)行時決定的.
那么接下我們通過代碼解析,來一步一步解釋何為閉包.
閉包
可能會有一些老司機會告訴你,在JavaScript中閉包就是一個嵌套函數(shù)...
用一段簡單粗暴的代碼來告訴你,啥是閉包:
function showClosure() {
var str = "閉包";
return function() { // 這就是一個閉包
return str;
};
}
showClosure();
我們來分析一下這段代碼:上面這段代碼定義了一個showClosure()函數(shù),該函數(shù)又將一個匿名函數(shù)作為返回值返回.這里就形成了一個閉包.在最后調(diào)用showClosure()函數(shù)的時候其定義時的作用域鏈仍然是有用的,這并不會影響里面的閉包.而且showClosure()返回了一個匿名函數(shù)/閉包,這里有很多微妙的事情可以說:
- 閉包的值捕獲
- 此時垃圾機制的運作和作用域鏈的關(guān)系
- 值捕獲導(dǎo)致的內(nèi)存問題
- this關(guān)鍵字在其中的使用
接下來我們一一分析;
值捕獲
值捕獲是閉包的一個非常強大的特性,閉包可以捕獲到局部變量(或參數(shù)),并一直保存下來,駐留在內(nèi)存中;
這個特性可以解決全局變量的污染問題(全局變量污染導(dǎo)致應(yīng)用程序不可預(yù)測性,每個模塊都可調(diào)用必將引來災(zāi)難, 所以推薦使用私有的,封裝的局部變量),在很多開源框架中普遍使用;
我們再看看上面那段代碼,然后根據(jù)代碼來分析:
function showClosure() {
var str = "閉包";
return function() {
return str;
};
}
console.log(showClosure()());
注意,在外圍函數(shù)中有一個值為”閉包”的局部變量str.按照我們常規(guī)的思維,在showClosure函數(shù)執(zhí)行到最后一個”}”的時候,里面所有的值應(yīng)該都會被垃圾回收機制回收掉才對,然而我們調(diào)用該函數(shù)的時候其結(jié)果仍然能夠返回str的值.這就是因為里面的閉包捕獲了str,導(dǎo)致其會駐留在內(nèi)存中不被釋放,所以我們后面調(diào)用的時候仍然有值;很多人很是不解,這其實跟作用域鏈原理有關(guān):
JavaScript中的作用域鏈(scope chain)是一個列表對象,而不是像其他語言是綁定的棧(stack);每次調(diào)用JavaScript函數(shù)的時候,都會為之創(chuàng)建一個新的對象來保存局部變量,把這個對象添加至作用域鏈中;當函數(shù)返回的時候,就從作用域鏈中將這個綁定的變量的對象刪除;如果不存在嵌套的函數(shù),也就沒有其他引用指向這個綁定對象,它就會被當做垃圾回收掉;如果定義了嵌套的函數(shù),每一個嵌套函數(shù)又都各自對應(yīng)一個作用域鏈,并且這個作用域鏈指向一個變量綁定對象.但如果這些嵌套的函數(shù)對象在外部函數(shù)中保存下來,那么它們呢也會和所指向的變量綁定對象一樣當做垃圾回收掉;但是如果這個函數(shù)定義了嵌套函數(shù),并且將它作為返回值或者被存儲下來,這是就會有一個外部引用指向這個嵌套的函數(shù).它就不會被當做垃圾回收,并且他所指向的變量綁定對象也不會被當做垃圾回收掉.
所以,其實是在調(diào)用showClosure函數(shù)的時候,str并沒有被釋放,因為他被作為返回值函數(shù)的返回值返回了(有點兒繞??);
看到這里有些同學(xué)可能就會看出另一個問題,閉包會引起內(nèi)存泄露,因為他會捕獲一些值存儲下來,導(dǎo)致后面不能釋放,除非瀏覽器被關(guān)閉;
內(nèi)存問題
既然值被捕獲了,系統(tǒng)不能自動釋放,那么我們就自己手動釋放嘛;只需要將需要釋放的對象手動置為null即可;
示例:
function showClosure() {
var aDiv = document.getElementById("aDiv");
aDiv.onclick = function() {
console.log(aDiv.innerHTML);
}
aDiv = null; // 解除引用,如果沒有解除,在瀏覽器被關(guān)閉的時候才能被回收
}
閉包中的this
在使用閉包的時候有一個非常需要注意的東西:魔性的this!
在JavaScript中這個this非常容易用錯,即使是多年的老司機也是經(jīng)常翻車.
this如果在全局中調(diào)用其值為全局對象(非嚴格模式)或undefined(嚴格模式).那么大多數(shù)人會認為在閉包中使用this返回的應(yīng)該是當前函數(shù)對象或者是最外層的函數(shù)對象;然而在閉包中卻不是這樣,在閉包中this會隨著調(diào)用對象的不同所指代的對象不同,而非外層的函數(shù)對象;所以這里使用this的時候要使用一個小技巧,那就是把最外層對象的this保存下來;
看代碼:
// 注意這段代碼是執(zhí)行在非嚴格模式下的
// 在嚴格模式下這種方式會報錯,也就是說嚴格模式下不允許你這么寫
var name = "NAME";
var person = {
name : "name",
getUserName : function() {
return function() {
return this.name;
};
}
};
console.log(person.getUserName()());
這個東西在很多面試題上經(jīng)??吹?其實就是考你對閉包和作用域的理解;
執(zhí)行結(jié)果如下:
NAME
看到?jīng)],這種情況下this會指向全局對象,這樣this.name的值就是”NAME”;這如果想要讓this.name的值為”name”有兩種方法:
第一種,在調(diào)用的時候使用call函數(shù)強制傳一個對象給他.
console.log(person.getUserName().call(person));
第二種,保存外層對象的this:
var name = "NAME";
var person = {
name : "name",
getUserName : function() {
var self = this; // 將this保存至一個變量中
return function() {
return self.name;
};
}
};
console.log(person.getUserName()());
所以,還是記住那句話:函數(shù)的執(zhí)行依賴于變量作用域,這個作用域是函數(shù)定義時決定的,而不是函數(shù)執(zhí)行時決定的.
生命不息,折騰不止...
I'm not a real coder,but i love it so much!