閉包是JavaScript的一大難點,也是它的特色。利用閉包可以實現許多高級應用。今天我們就一起來討論一下有關閉包的問題。
很多語言中都有閉包,概念也不盡相同。就JS閉包的概念也有好多種,下面一種我認為是比較好理解也比較精確的一種。
閉包是指有權訪問另一個函數作用域中的變量的函數。
這句話可以分為兩步來理解:
1)閉包是函數;
2)這個函數可以訪問另一個函數作用域中的變量。
等等,作用域又是什麼鬼。。。
第二點翻譯一下就是可以訪問另一個函數內部定義的變量。下面再解釋什麼是作用域。
示例代碼:
function outer() {
var name = "Foolgry";
function inner() {
return name;
}
}
從技術層面來說,上面的inner
已經是一個閉包了。
1)它是一個函數;
2)它可以訪問outer
內部定義的變量name
。
但是,大多數人習慣的閉包是下面這個樣子的:
示例代碼:
function outer() {
var name = "Foolgry";
function inner() {
return name;
}
return inner;
}
var getName = outer();
getName(); // "Foolgry"
這個inner
肯定是閉包,它不僅滿足上面的兩個條件,還具備一個特征:即使outer
運行後被回收,它能夠保證name
在內存中繼續存在。
每個人都有不同的理解,有的人認為只有第二個例子才算閉包,第一個例子不算。我的理解是都可以算做閉包。
閉包本身並不難理解,難得是它和作用域,this等問題放在一起。
說起閉包,就不得不談一些基礎問題,比如:上面提到的作用域。
JS中有兩種作用域,全局作用域和局部作用域。
示例代碼:
var name = "Bob",//全局作用域下定義的變量(全局變量)
age = 99,
sex = "female";
function object() {
var name = "Foolgry"; //局部作用域下定義的變量(局部變量)
alert(name); // "Foolgry"
alert(age); // 99
alert(sex); // undefined
var sex = "male",
job = "fe"
}
alert(name); // "Foolgry"
alert(job); // error
object
作用域中,name是局部變量,雖然全局環境中也定義了變量name,但是在局部作用域中,會優先訪問局部變量;sex為什麼是undefined呢?因為JS中有一種機制叫做變量提升。這東西會把上面幾句話變成這樣:
var sex;
alert(sex);
sex = "male";
訪問job發生錯誤,是因為一般情況下局部變量只能在其函數作用域內部訪問,這也是為什麼需要閉包了。
閉包的應用有很多,這裡只列舉其中兩種:
1)由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存洩露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。
2)閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。
function fun(n,o) {
console.log(o);
return {
fun:function(m) {
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1); c.fun(2); c.fun(3);
問:三行a,b,c的輸出分別是什麼?
這個例子如果你能全部答對,那麼JS閉包就沒什麼問題了。
undefined,0,0,0
undefined,0,1,2
undefined,0,1,1
下一篇文章討論一下這題解法。