天天看點

循環一個節點清單(NodeList)或者數組,并且綁定事件處理函數引發對閉包的了解

循環一個節點清單(NodeList)或者數組,并且綁定事件處理函數引發對閉包的了解

    當然可以使用事件委托來實作,而且性能更好,但是這裡純粹為了了解這個問題。

    我在網上查閱了很多資料,有了一點了解,記錄下來。如果有不對的地方,各位看客請指正。

    代碼如下:

HTML結構
<div id='one'></div>
 <div id='two'></div>
 <div id='three'></div>
    
JS處理
    var odivs = document.getElementsByTagName('div');
    
    for(var i=0; i<odivs.length; i++){ //周遊節點清單并且綁定事件
        
        odivs[i].onclick = function(){
            
            alert(i);
        }
    }      

    當我們點選任何一個DIV時,彈出的都是3;

    這樣會非常讓人疑惑

    那麼我們換給寫法。

var one = document.getElementById('one');
    var two = document.getElementById('two');
    var three = document.getElementById('three');
    
    var i=0;
    
    one.onclick = function(){
        alert(i);
    }
    
    i++;
    
    two.onclick = function(){
        alert(i);    
    }
    
    i++;
    
    three.onclick = function(){
        alert(i);    
    }
    
    i++ // i=3  大于2 退出循環      

    從這裡知道,3這邊值是i的最終的值。

    然後我們再看事件處理函數。

    function(){

        alert(i);    

    }

    從這裡可以看出,這是一個函數聲明,在面向對象中,這叫構造函數。

    這隻是一個函數聲明,并且綁定到點選事件上,并沒有被執行。然後代碼繼續往下,執行i++;

    在網上看到這樣一句話:

    之是以是這樣的,看過《JavaScript: The Definitive Guide》就會明白:

所有的JavaScript函數都是閉包:它們都是對象,都關聯到作用域鍊。每次調用JavaScript函數的時候,都會為之建立一個新的對象來儲存局部變量,并把這個對象添加至作用域鍊中(簡單了解:作用域鍊就是對象清單)。

    重點:"每次調用javascript函數時",局部變量會在函數被執行的時候建立,而不是聲明的時候建立。

    上面的例子,我們很直覺的看出。三個函數中的i共享全局變量i;事件處理函數并沒有被執行,代碼接着往下走,

    一直到腳本結束。 i的值變為3.

    這時候,點選其中任意一個div,然後顯示的是全局的i,i=3,是以得出了3這個值。

    再往下看,可能有人會這麼寫,我就這麼寫過。

    for(var i=0; i<odivs.length; i++){

        odiv[i].onclick = function(){

            return function(o){

                alert(o);

            }(i);

        }

    }

    一看之下,好像沒什麼問題,變量i被一個内部函數儲存了下來。函數也被執行了。

    但是,函數真的被執行了嗎?

    分拆開來

odiv[i].onclick = function(){

            return function(o){

                alert(o);

            }(i);

    }

    綁定了一個事件函數

    function(){

            return function(o){

                alert(o);

            }(i);

    }

    可以看出,并沒有被執行。代碼還是繼續往下走。

    并且代碼也不會被執行。當時不知道怎麼就寫了這麼個東西。

    js 中隻有 function 才會劃分作用域(和 python 有點像),if/else、for 循環都不會劃分作用域,是以原來的方式循環引用的都是同一個變量 i。

    以上,我們可以得出一個結論:

    要想把i的值儲存下來。必須在執行過程中把i放到一個函數中,并且函數需要立即執行,這樣0,1,2的值才會被保留下來。

    那麼修改代碼

    for(var i=0; i<odivs.length; i++){

        odivs[i].onclick = show(i);

    }

    function show(i){

        alert(i);

    }

    那麼,這樣寫行嗎。顯然是不行的。

    雖然在綁定事件的時候,調用了一個外部函數,并且将i傳遞進去儲存了下來。

    但是,結果會是沒循環一次,調用一次show函數。然後彈出一個數字。并沒有在onclick并沒有綁定事件處理函數,因為show函數并沒有傳回值,或者傳回值為undefined ,是以上面的代碼應該是這樣的

    for(var i=0; i<odivs.length; i++){

        show(i);

        odivs[i].onclick = undefined;

    }

    好吧,以上是我在學習過程中犯的一些錯誤,和自己慢慢深入後的一些了解。下面正式來說說解決這個問題的辦法。

    上面說過:

    1.要想把i的值儲存下來。必須在執行過程中把i放到一個函數中,并且函數需要立即執行。

    2.函數的傳回值應該是一個事件處理函數。否則事件綁定就沒有意義了。

    修改後代碼如下。

for(var i=0; i<odivs.length; i++){
        
        odivs[i].onclick = show(i); // 講變量i傳入函數立即執行,保留下來。
    
    }
    
    function show(i){
        
        return function(){  //傳回一個事件處理函數。函數内部調用alert的方法将i顯示出來。
            alert(i);
        }
        
    }      

    //結果 0,1,2 正确。

    然後我們寫在一起

    通過一個佚名函數實作,道理和上面一樣。

for(var i=0; i<odivs.length; i++){
        
        odivs[i].onclick = (function(i){
                
                return function(){
                        alert(i);
                    }
                
            })(i);
    
    }      

    當然還可以這麼寫

    先把i保留下來,再綁定事件。順序反一下。

for(var i=0; i<odivs.length; i++){
        
        (function(i){
            
            odivs[i].onclick = function(){
                alert(i);
            }
            
        })(i);
        
    }      

至此,這個問題處理完畢。

文章中很多來自 http://www.zhihu.com/question/20019257 ,感謝!

百度搜了很多這個問題,排前面的都是幾個li,然後出現這個問題,然後就是一個解決辦法,而且大多數網站都是這樣,抄來抄去,連标點都一樣,很讓人困惑,浪費事件。

上面這篇内容對我啟發很大。講講自己的了解。

我來講講自己的了解。

首先說了,JS其實也分傳值和傳位址 也就是 by value and by sharing. 對應java,C等語言的 by value 和 by reference

var bar;

var foo = bar;

bar = {'key' : 'value'};

console.log(foo , bar );

這樣一個案例。

輸出的是 undefined 和 {'key':value};

從這裡可以看出,foo=bar 是把 bar的值傳遞給了 foo ; 因為一開始 bar沒有指派,值是undefined,是以foo也是undefined,bar再指派。

假如foo=bar 是将 bar的引用傳遞給了 foo的話,那麼 修改bar的值, foo輸出的也應該是修改後的值。

再看如下代碼

var d = new Date();

console.log(d.getMinutes());

var a = d;

a.setMinutes(18);

console.log(d.getMinutes());

// 18

測試的時候是 48 18

很明顯, a = d 時,是将 引用傳遞給了 a ,修改a的屬性時,d也跟着變了。

看JS中的作用域(scope) 看原文,已經寫的不錯了。

自 http://www.zhihu.com/question/20019257

個人的一點心得,還在學習中,如有不對的地方,還望指出。