第一次接觸這個問題還是在我剛開始學js的時候,當時就是一頭霧水,時隔一年多了,突然又想起了這個問題,在這個春氣盎然的周末,我就坐下來研究下並把結果和大家分享下;
先看代碼:demo.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="gbk"/>
<title>閉包循環問題</title>
<style type="text/css">
p {background:red;}
</style>
</head>
<body>
<p id="p0">段落0</p>
<p id="p1">段落1</p>
<p id="p2">段落2</p>
<p id="p3">段落3</p>
<p id="p4">段落4</p>
<script type="text/javascript">
for( var i=0; i<5; i++ ) {
document.getElementById("p"+i).onclick=function() {
alert(i); //訪問了父函數的變量i, 閉包
};
};
</script>
</body>
</html>
每次循環就為對應的編號段落上添加一個click事件,事件的回調函數是執行一個alert();如果你以前沒這么用過的話,估計也會認為單擊某個段落就會彈出這個段落相應的編號0,1,2,3,4。但實際上是都是彈出5;
網上已經有很多討論的博客了,他們給出了很多方法去實現彈出對應的編號。比較易於理解的方法如下:
1,將變量i保存在對應的段落的某個屬性上:點擊查看效果。
var pAry = document.getElementsByTagName("p"); for( var i=0; i< 5; i++ ) { pAry[i].no = i; pAry[i].onclick = function() { alert(this.no); } };
2,加一層閉包,i 以函數參數形式傳遞給內層函數:點擊查看效果。
~function test() { var pAry = document.getElementsByTagName("p"); for( var i=0; i< pAry.length; i++ ) { (function(arg){ pAry[i].onclick = function() { alert(arg); }; })(i);//調用時參數 } }();
當然還有其他一些方法,但是都不太好理解。
而我要探索的是,為什么demo.html中的返回值始終是5。網上的說法是“變量i是以指針或者變量地址方式保存在函數中”,因為只有按照這樣理解,才能解釋。可是僅僅憑借一個結論怎么才能服眾了?
談到指針或者變量地址這個話題,在C語言中倒是家常便飯了,但是在js這么性感的語言中,除了對象的及其對象屬性的引用之外很少用到。一個基本的數據類型居然和指針拉上關系了,這勾起了探索的欲望。
3,試試下面的代碼 點擊查看效果
~function test() { var temp =10; for( var i=0; i< 5; i++ ) { document.getElementById("p"+i).onclick=function() { alert(temp); //訪問了父函數的變量temp, 閉包 } }; temp=20; }();
它的執行結果是每個段落的彈出都是20,而不是10。說明當我們在單擊的那個時候,temp的值已經是20。這是個似乎不需要我來說明,很顯然的結果,因為在我們單擊之前,temp已經被賦值為20了。
4,再看看下面的代碼 點擊查看效果。
我們在temp被改變值之前有程序去觸發單擊事件,彈出的是10;
~function test() { var temp =10; for( var i=0; i< 5; i++ ) { document.getElementById("p"+i).onclick=function() { alert(temp); //訪問了父函數的變量i, 閉包 } if(i===1){ var evt = document.createEvent("MouseEvents"); evt.initEvent("click",true,true); document.getElementById("p1").dispatchEvent(evt); } }; temp=20; }();
這說明我們在p1上綁定的單擊事件回調函數本來是要返回10的,當我再次手動去單擊p0段落時,彈出20的原因是因為temp的值改變了。也就說明,每次彈出時,訪問到的是temp此刻的值,而不是綁定時候的值;這可以說明變量temp確實是以變量地址保存的。擴展開去就是:函數內部訪問了與函數同級的變量,那么該變量是常駐內存的。訪問該變量實質上是訪問的是變量的地址;
通過以上的結論,那么我們可以簡單的描述閉包的本質了:在子作用域中保存了一份在父級作用域取得的變量,這些變量不會隨父級作用域的銷毀而銷毀,因為他們已經常駐內存了!
這句話也就說明了閉包的特性了:
1:因為常駐內存所以會造成內存泄露
2,只要其他作用域能取到子作用域的訪問接口,那么其他作用域就有方法訪問該子作用域父級作用域的變量了。
看這樣一典型的閉包的例子:
function A(){
var a=1;
function B(){
return a;
};
return B;
};
var C=A();//C取得A的子作用域B的訪問接口
console.log(C());//1 C能訪問到B的父級作用域中的變量a
以上若有不足之處,歡迎指正,共同進步!