概念
簡單理解:函數中的函數,就是閉包。也可以說是:有權訪問另一個函數作用域中的變量的函數。
閉包也是函數,只不過比較特殊。
閉包的優點,也是它的缺點:可以把局部變量駐留在內存中,因此可以避免使用全局變量。當代碼量足夠大時,使用全局變量會發生命名的沖突、各個地方的對全局變量的修改而導致的一系列問題。
由于閉包會導致局部變量不被回收,可能會消耗內存。因此,在閉包使用結束后,應將閉包設置為 null 。
function f(){
var age = 100;
return function(){
age++;
return age;
}
}
var fi = f();
for(var x = 0;x<10;x++){
console.log(fi())
}
fi = null;//回收閉包
使用閉包時,閉包的外部函數應該只調用一次。因為每調用一次外部函數,函數中的局部變量就會被重新初始化。上例中,只調用了一次 f(),保證 age 只被寢化一次。
-
各個閉包函數共享外部函數的所有變量,一個函數對變量進行修改,所有的閉包函數都會隨之變化。
function Outer(value){ var a = value; m1 = function(){ console.log('m1.a = '+a) a = 'm1' } m2 = function(){ console.log('m2.a = '+a) a = 'm2' } } Outer('init') m1() // m1.a = init m2() // m2.a = m1 m1() // m1.a = m2
m1,m2 各輸出當前 a 的值,并進行修改。可以發現,m2 輸出的就是 m1 修改后的值。而 m1 輸出的也是 m2 修改后的值。
取值問題
在下面代碼中,輸出的結果是 5。
function Fun () {
var r = [];
for(var x = 0;x<5;x++){
r[x] = function(){
return x;
}
}
return r;
}
console.log(Fun()[0]()) // 輸出的是 5
這是因為在執行完 Fun() 時,函數內部的循環已經執行完畢,x 值也變成了 5。所以在執行 r 的第一個函數時,取到 x 值就是 5。
可以改成如下形式:
function Fun () {
var r = [];
for(var x = 0;x<5;x++){
r[x] = (function(num){
return function inner(){
return num
}
})(x) // 自動執行匿名函數
}
return r;
}
var r = Fun()
for(var x = 0;x<5;x++){
console.log(r[x]()) // 依次輸出0到4
}
匿名函數執行時,會將 x 值傳遞到匿名函數中,而匿名函數內部有一個閉包,會將傳入的 x 值駐留在內存中。這導致每一個匿名函數內部的閉包對應不同的 num 值。因此,在輸出是輸出的是 0 到 4。
閉包中的 this
由于閉包也是一個函數,所以它中的 this 指向了運行該函數的對象。
如:
var age = "window.age"
var o = {
age:'o.age',
getThis:function(){
return this.age;
}
}
console.log(o.getThis()) // a.age
var o2 = {
age:'o2.age',
getThis:function(){
return function(){
return this.age;
}
}
}
console.log(o2.getThis()()) // window.age
var o3 = {
age:'o3.age'
}
console.log(o2.getThis().call(o3)) //o3.age
第二次輸出時,相當于在 window 下直接運行了閉包,所以閉包中的 this 指向了 window 中的 age 屬性。
第三次輸出時,通過對象冒充的方式,使得閉包在 o3 對象中運行,所以閉包中的 this 指向了 o3 中的 age 屬性。
指向外部函數
由于閉包執行時,this 指向的是調用閉包的對象。如果想讓 this 指向閉包的外部函數,可以如下修改:
var o2 = {
age:'o2.age',
getThis:function(){
var that = this; // 用 that 變量記錄執行外部函數時 this 的指向
return function(){
return that.age; // 閉包,保證 that 變量的全局性
}
}
}
console.log(o2.getThis()()) // o2.age
在外部函數中使用一個變量記錄外部函數的 this,并在閉包中使用該變量代替
this,由于閉包的特性,可以保證該變量能常駐內存,所以在執行閉包時該變量就表示了外部函數的 this 。
模仿塊級作用域
js 中沒有塊級作用域。if for ,while 等語句執行完畢后,{} 內的變量外界依舊可以訪問。但,函數內部的變量卻是局部變量。所以, 可以使用匿名函數模仿塊級作用域——使用一個自執行的匿名函數將要分塊代碼包裹起來即可
for(var i = 0;i<10;i++){}
console.log(i); // 10
(function(){
for(var x = 0;x<10;x++){}
})()
console.log(x) // 此處訪問 x 出錯