完全弄懂js循環中的閉包問題

   第一次接觸這個問題仍是在我剛開始學js的時候,當時就是一頭霧水,時隔一年多了,忽然又想起了這個問題,在這個春氣盎然的週末,我就坐下來研究下並把結果和你們分享下;javascript

先看代碼: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;css

網上已經有不少討論的博客了,他們給出了不少方法去實現彈出對應的編號。比較易於理解的方法以下:html

1,將變量i保存在對應的段落的某個屬性上點擊查看效果。java

     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這麼性感的語言中,除了對象的及其對象屬性的引用以外不多用到。一個基本的數據類型竟然和指針拉上關係了,這勾起了探索的慾望。this

3,試試下面的代碼 點擊查看效果 spa

~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確實是以變量地址保存的。擴展開去就是:函數內部訪問了與函數同級的變量,那麼該變量是常駐內存的。訪問該變量實質上是訪問的是變量的地址;htm

   經過以上的結論,那麼咱們能夠簡單的描述閉包的本質了:在子做用域中保存了一份在父級做用域取得的變量,這些變量不會隨父級做用域的銷燬而銷燬,由於他們已常常駐內存了!

這句話也就說明了閉包的特性了:
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 以上如有不足之處,歡迎指正,共同進步!

相關文章
相關標籤/搜索