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