1.閉包與變量
JavaScript中的作用域鏈的機制引出了一個副作用,即閉包只能取得包含函數中任何變量的最后一個值。閉包所保存的是整個變量對象,而不是某個特殊的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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++){
document.write(funcs[i]() +
"<br />"
);
}
|
createFunction()函數返回一個數組。表面上看,似乎每個函數都應該返回自己的索引值,但事實並非如此,事實上每個函數的返回值都是10.因為每個函數的作用域鏈中都包含着createFunctions()函數的活動對象,所以它們引用的都是同一個變量i。當createFunctions()函數返回后,變量i的值就是10,此時每個函數都引用着保存變量i的同一個變量對象,所以每個函數返回后都是10.
當然我們可以使用匿名函數強制使閉包的行為符合預期。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
function
createFunctions(){
var
result=
new
Array();
for
(
var
i=0;i<10;i++){
result[i]=
function
(num){
return
function
(){
return
num;
};
}(i);
}
return
result;
}
var
funcs = createFunctions();
for
(
var
i=0; i < funcs.length; i++){
document.write(funcs[i]() +
"<br />"
);
}
|
在重寫了前面的createFunctions()函數后,每個函數就好返回各自不同的索引值了。在這里,我們沒有直接把閉包賦值給數值,而是定義了一個匿名函數,並將立即執行該函數的結果賦值給數組。這里的匿名函數有一個參數num,也就是最終的函數要返回的值。在調用每個匿名函數時,我們傳入了變量i。由於函數參數按值傳遞的,所以就會將變量i的當前值復制給參數num。而在這個匿名函數內部,有創建並返回了一個訪問num的閉包。這樣依賴,result數組中的每個函數都有自己num變量的一個副本,因此就可以返回各自不同的數值了。
1.2關於this對象
在閉包中使用this對象會出現一些問題,this對象是運行時基於函數的執行環境綁定的:在全局函數中,this等於window,而當函數被當作某個對象的方法調用時,this等於那個對象。不過,匿名函數的執行環境具有全局性,因此其this對象通常指向window(當然,在通過call()和apply()改變函數執行環境時,this指向其他對象)。
1
2
3
4
5
6
7
8
9
10
11
|
var
name=
"The Window"
;
var
object={
name:
"My object"
,
getNameFunc:
function
(){
return
function
(){
return
this
.name;
};
}
};
alert(object.getNameFunc()());
//"The Window"(在非嚴格模式下)
|
以上代碼創建了一個全局變量name,有創建了一個包含那么屬性的對象,這個對象還包括一個方法——getNameFunc(),它返回一個匿名函數,而匿名函數又返回this.name.由於getNameFunc()返會一個函數。因此調用object.getNameFunc()()就會立即返回調用它的函數,結果就返回一個字符串。然而,這個例子返回的字符串是“The Window”,即全局name變量的值。
但是,為什么匿名函數沒有取得其包含作用域(或外部作用域)的this對象呢?
每個函數在調用時,其活動對象都會自動獲取兩個特殊的變量:this和arguments。內部函數在搜索這兩個變量時,只會搜到其活動對象為止,因此永遠不肯能訪問到外部函數中的這兩個變量。不過,把外部作用域中的this對象保存在一個閉包能夠訪問的變量里,就可以放閉包訪問該對象了。
1
2
3
4
5
6
7
8
9
10
11
12
|
var
name=
"The Window"
;
var
object={
name:
"My object"
,
getNameFunc:
function
(){
var
that=
this
;
return
function
(){
return
that.name;
};
}
};
alert(object.getNameFunc()());
//"My object"
|
以上代碼中,我們在定義匿名函數之前,把this對象賦值給了that變量,而在定義閉包之后,閉包也可以訪問這個變量,因為它們是我們在外部函數中特意聲明的一個變量。即使在函數返回之后,this也仍然引用的object,所以調用object.getName()()就返回“My object”.
1.3內存泄漏
由於IE9之前的版本對JS對象和COM對象使用不同的垃圾回收歷程,因此閉包在IE中會導致一些特殊的問題。具體來說,如果閉包的作用域鏈中保存着一個HTML元素,那么就意味着該元素將無法被銷毀。
1
2
3
4
5
6
|
function
assignHandler(){
var
element=document.getElementById(
"someElement"
);
element.onclick=
function
(){
alert(element.id);
};
}
|
以上代碼創建了一個作為element元素事件處理程序的閉包,而這個閉包則有創建了一個循環引用。由於匿名函數保存了對assignHandler()的活動對象的引用,因此就會導致無法減少element的引用數,也就無法回收該對象。
不過可以通過一下代碼來解決。
1
2
3
4
5
6
7
8
9
|
function
assignHandler(){
var
element=document.getElementById(
"someElement"
);
var
id=element.id;
element.onclick=
function
(){
alert(id);
};
element=
null
;
}
|