從js的循環問題來看待js的閉包本質

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   
複製代碼

 

以上若有不足之處,歡迎指正,共同進步!


發佈了1 篇原創文章 · 獲贊 6 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章