JS閉包致使循環給按鈕添加事件時老是執行最後一個

就是有不定個的按鈕,且點擊按鈕時都須要執行一個方法(參數不同)javascript

那麼我很天然的就想到了,循環給每一個按鈕添加事件和參數就行了,因爲不方便上傳系統代碼,下面以一個簡單例子來講明.css

<!DOCTYPE HTML>
 <html>
  <head>
   <meta charset="utf-8"/>
   <title>閉包循環問題</title>
   <style type="text/css">
     p {background:red;}   </style>
 </head> 
 <body> 
  <p class="p">我是1號</p>
  <p class="p">我是2號</p>
  <p class="p">我是3號</p>
  <p class="p">我是4號</p>
  <p class="p">我是五號</p> 
</body> 
</html>
閉包循環問題

好比如今要實現這麼一個功能,在頁面上點擊上面的按鈕1到按鈕5時分別alert出1,2,3,4,5.html

那麼不少人天然想到以下這麼作:java

加入以下腳本代碼:windows

<script type="text/javascript">
    var page = document.getElementsByTagName("p");for(var i=0; i<page.length; i++) {
    page[i].onclick = function () {
        alert("我是"+i+"號");
      }
    } 
</script>

運行後,奇怪的發現不管點擊那個li標籤,alert出的都是最後一個的內容-"我是5號"閉包

你無論點擊哪個,都alert」我是5號「;
緣由就是你循環了五次產生了五個閉包,而這5個閉包共享一個變量i,說的明白一點就是,在for循環結束時,只是把這五個匿名函數註冊給click事件,當時在循環的時候並無執行,當循環結束了,此時i的值是5;以後你去點擊p標籤,你點擊哪個就執行哪個對應的匿名函數(這個時候才執行),這時候匿名中發現一個i,匿名中沒有定義i,因而沿着做用域鏈找,找到了,可是這時候循環早就結束了,i等於5,因而彈出」我是5號「來;點擊其餘的同理;
app

怎麼解決呢函數

既然已經知道函數調用外部變量的時候就構成了一個閉包,裏面的變量會受到別的地方的影響,那麼咱們
post

如今要作的就是,構建一個只有本身自己纔可訪問的閉包,保存只供自己使用的變量測試

構建一個閉包很簡單,代碼以下:

方式一:

<script>
    var list_obj = document.getElementsByTagName('p');    
    for (var i = 0; i <= list_obj.length; i++) {  
        list_obj[i].onclick = (function(i){ // outer function  
            return function(){ //inner function  
                alert(i);  
            };  
        })(i);    
    }
</script>

方式二:

<script>
    var list_obj = document.getElementsByTagName('p');    
    for (var i = 0; i <= list_obj.length; i++) {    
       (function(i){  
            //var p = i      
            list_obj[i].onclick = function() {        
            alert(i);        
            }  
       })(i);  
    }
</script>

方法三:

<script>
      var page = document.getElementsByTagName("p");for(var i=0; i<page.length; i++) {
      page[i].num = i//先把每一個變量值存起來
      page[i].onclick = function () {
        alert("我是"+this.num+"號");
      }
} 
</script>

閉包中的"this"對象:

<script>
    var num = 1;
    var obj = {
      num:2,
      getNum:function() {    
          return function () {      
              return this.num;
          }
       }
    }    
    alert(obj.getNum()());//num -> 1
</script>

爲何不彈出2呢,這裏是說明閉包中你須要注意如今的this的指向那一個對象,其實記住一句話就永遠不會用錯this的指向問題,this永遠指向調用它的做用域;

若是這樣寫你就可能理解了

<script>
    var num = 1;
    var obj = {
        num:2,
        getNum:function() {    
           return function () {      
              return this.num;
            }
        }
    }
    var a = obj.getNum();
    alert(window.a());//1
</script>

實際上是window對象調用的,this指的是windows,這就是說閉包中的this讓你看不清this的指向;

要是讓它alert 2你要這樣:

<script>
        var num = 1;var obj = {
            num:2,
            getNum:function() {    
                var _this = this;//在這裏保存this    
                return function () {      
                    return _this.num;
                }
            }
        }
        var a = obj.getNum();
        alert(window.a());
</script>


其實,對於閉包沒有太多的概念,也沒有深刻的去研究,把本身在項目中碰到的一個例子拿出來寫寫:

具體頁面展現是這樣的:循環生成動態菜單,而後給每一個生成的菜單添加對應的onclick事件,

在代碼中是這麼寫的:

html:代碼
<a id="bb" href="javascript:void(0);" class="easyui-menubutton" data-options="menu:'#layout_north_stMenu222',iconCls:'icon-cologne-sign-out'">導出</a>
         <div id="layout_north_stMenu222" style="width: 150px; display: none;">
	          <div class="menu-sep"></div>
	          <div id="_download_1" data-options="iconCls: 'icon-hamburg-docs'">導出Excel</div>                    <div id='mdm'>到工程//在"findItem"中是根據div中的文原本的查找的
		           <div  id='cprjList'></div>
                  </div>
JS代碼:
$.post(url,data,function(data){
    if (data.rows && data.total>0)
	{
		var menubutton = $($('#bb').menubutton('options').menu);//得到下拉菜單的button
		var menu = menubutton.menu('findItem','到工程');//得到菜單項'到工程',這個是div的名字
		for(var i=0;i<data.total;i++)////data.rows就兩條記錄,因此i=0,i=1
		    {
			menubutton.menu('appendItem',{
			parent:menu.target,//給"到工程"這個菜單項動態增長子項
			text:data.rows[i].name,
			iconCls: 'icon-hamburg-docs',
//onclick:function(){accoToCprj(console.info(i);data.rows[i].id,data.rows[i].name);}開始的寫法
			onclick:(function(i){//給返回的函數傳入參數i
				return function(){//因而在返回的這個函數的做用域就可使用參數i
				    console.info(i);
				    accoToCprj(data.rows[i].id,data.rows[i].name);
				};
			    })(i)
			});
		}
		return;
	}
})

開始的寫法是:

onclick:function(){accoToCprj(console.info(i);data.rows[i].id,data.rows[i].name);}

怎麼測試,發現彈出來的都是2,一直摸不着頭腦,網上搜了一下,才發現問題,由於生成菜單後,點擊觸發onclick事件,就會執行accoToCprj()方法,由於此時i已經不是0,也不是1,而是2,由於在循環過程當中,在i=0,i=1的時候生成兩個菜單,而後i=2的時候跳出循環了,因此在觸發onclick的時候,此時找到的i=2,因此一直彈出的是"2",因而在網上面找資料,把變量i存儲起來,在單擊的時候,返回一個函數,該返回的函數中能夠調用這個閉包中的變量i...在新寫的函數中,發現彈出的是0,1,總算正確了...

相關文章
相關標籤/搜索