時間: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機制導致的問題。