DOM的addEventListener函數一個陷阱。


時間:2015年7月9日

這個函數是一個DOM Level3級方法,注冊事件的,用法是:

DOM對象.addEventListener("事件名稱(比如click)",事件執行的函數(可以是匿名函數或者函數名),false/true(false表示冒泡方式,true表示捕獲方式));

這里注意第二個參數好像不是立即就編譯進去(當然JS是沒有編譯的東西的,只是為了表述),而是在事件觸發的時候才運行里邊的代碼,包括變量賦值。

比如下邊代碼

<a href="###">連接一</a>
<a href="###">連接二</a>
<a href="###">連接三</a>
<a href="###">連接四</a>
<a href="###">連接五</a>
<a href="###">連接六</a>
<a href="###">連接七</a>
<a href="###">連接八</a>
<a href="###">連接九</a>

<script>
var myHref = document.getElementsByTagName("a");
for (var i = 0,mylength = myHref.length; i<mylength; i++) {
    myHref[i].addEventListener("click",function(e){
        e.preventDefault();
        alert(i);
    },"false");
}
</script>

我們希望的結果是點擊每一個a會彈出對應的數組標號值,也就是第一個a點擊后顯示0,第二個顯示1。但是實際上我們發現並不是這個結果,而是總是顯示最后一個a標簽對象的數組標號值+1,也就是9。

所以我認為,函數並不是立即執行,而只是發函數的引用放在那了,當事件發生的時候,才去調用實際的函數體,而且i做為全局變量,一直保存了下來,如果我們最后對i進行了別的操作,比如這樣:

<script>
var myHref = document.getElementsByTagName("a");
for (var i = 0,mylength = myHref.length; i<mylength; i++) {
    myHref[i].addEventListener("click",function(e){
        e.preventDefault();
        alert(i);
    },"false");
}
i=555;
</script>

每一個連接彈出的就是555了。實際i是做為全局變量的(js沒有塊作用域,所以for循環內的i不是局部變量,而是全局變量),我又想,如果我把i讓內存回收掉,是不是就會報錯了,結果我試了一下並不能回收,可能是因為閉包的關系。其實瀏覽器里即使是全局函數都是一個閉包,具體為什么我這里理解,大家可以找一找閉包的相關資料看一下。

那么如何實現我們想要的效果,還是得用到閉包啊,我們需要把當時的i保存下來。代碼如下:

<script>
var myHref = document.getElementsByTagName("a");
for (var i = 0,mylength = myHref.length; i<mylength; i++) {
    (function(i){  //這里的i跟外部的i實際不是一個i
        myHref[i].addEventListener("click",function(e){
            e.preventDefault();
            alert(i);
        },"false");
    })(i);
}
i=555;    //不會影響
</script>

這樣寫,實際上for循環里是一個立即執行的函數表達式,這種寫法()();是立即執行了,立即執行我們的閉包,我們把i當參數傳給閉包,閉包里邊的i實際作用域只在閉包里,跟外部的i不是一個i,因此就能實現我們想要的效果了。

總結的不好,見諒。

 

-----2015年7月10日,我今天又腦洞大開了一下,為什么會出現這個陷阱。因為click事件(或者所有的DOM事件?反正setTimeout和setInterval也是回調機制處理)是回調機制處理的,由於javaScript是單線程的語言,它會按照javaScript語法出現的次序順序執行(聲明提升實際在這之前發生的),但是遇到click等事件的時候,並不是立刻就執行click對應的函數了了,只是把click事件的函數引用放在一個隊列里,繼續執行下邊的代碼,等所有代碼執行完畢,就查看任務隊列里找第一個,就執行前邊存放的click事件的引用,而那時候才進行了所有的賦值,判斷等操作,也就是i無法保留的原因。

按照上邊有問題的代碼來解釋:

javascript會先執行getElementsByTagName,然后執行for,for里邊有一個addEventListener,這個函數也會執行,但是注意,addEventLister的第二個參數,並不是立刻執行了,只是發函數引用放在那了,然后一直到for循環,再然后執行i=555。

注意:javascript除了主線程,還有一個任務隊列的東西,主線程執行完畢了,就去隊列找任務,當然我們不點擊的話,任務隊列就是空的,當我們點擊了,addEventLister就會把他的第二個參數的函數放到隊列里,然后javaScript主線程突然發現隊列里有東西了,趕緊拿出來用吧,但是那時候,大媽已經不是以前的大媽了。。。。。。。

所以,其實也不是陷阱,是javaScript機制導致的問題。

阮大俠的博客有介紹,event loop機制的問題。


免責聲明!

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



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