徹底弄懂js循環中的閉包問題


   第一次接觸這個問題還是在我剛開始學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 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM