原文地址:http://tobyho.com/2011/11/02/callbacks-in-loops/
某些時候,你需要在迴圈裡建立一個回撥函式。我們來試試給頁面裡每個連結增加點選事件。
var links = documnet.getElementsByTagName('a') for (var i = 0, len = links.length; i < len; i++){ // Note: `addEventListener` is standard compliant browsers only links[i].addEventListener('click', function(e){ alert('You clicked on link ' + i) }, false) }
當你點選連結的時候,你將開啟一個警告框告訴你被點選的連結索引。很好用吧,但是,這不會發生。
實際上,每次警告框都會彈出"You clicked on link 5" - 假如你有5個連結。發生這樣的原因是你建立的每一個回撥函式(每個迴圈迭代)都指向了相同的變數i。也就是說,儘管var i在迴圈內被定義,但他不在迴圈範圍內,即新的i並不是被迴圈構造出的。其實,我就算在迴圈外宣告,也是一樣的結果。
1 var links = documnet.getElementsByTagName('a') 2 var i 3 for (i = 0, len = links.length; i < len; i++){ 4 /* blah blah */ 5 }
解決這個問題的方法是建立一個outer
函式,並傳遞引數i來了-然後立刻執行它。
1 var links = documnet.getElementsByTagName('a') 2 for (var i = 0, len = links.length; i < len; i++){ 3 !function outer(i){ 4 links[i].addEventListener('click', function inner(e){ 5 alert('You clicked on link ' + i) 6 }, false) 7 }(i) 8 }
注:我還定義了回撥處理函式inner
,以區分這兩個函式。
outer
的功能也被稱為IIFE - 它被建立後立即執行,然後銷燬。 (還有其他的方法來寫 - 我只是喜歡的版本的!)我們需要它是因為我們需要一個新的變數範圍,並在Javascript中,函式是唯一能這麼做的辦法。
要注意在outer函式外部,i仍然是迴圈中定義的i,但在inside函式內,i只是被宣告的一個區域性變數, - 它優先於outside函式中的i。
1 !function(i){ // <-- inside `i` 2 /* here, `i` refers to inside `i` */ 3 }(i) // <-- still outside `i`
我們可以將inside函式中的變數i改成ii,這樣看起來清楚一點。
1 var links = documnet.getElementsByTagName('a') 2 for (var i = 0, len = links.length; i < len; i++){ 3 !function outer(ii){ 4 links[i].addEventListener('click', function inner(e){ 5 alert('You clicked on link ' + ii) 6 }, false) 7 }(i) 8 }
現在ii變數在outer函式裡,變數i在外面。因為閉包,ii將一直存在直到outer函式結束(單擊事件發生的時候)。換句話說,inner函式將ii變數固定,任何程式碼都可以訪問。
更新
Jonprins評論說道,建立一個函式的開銷是昂貴的,所以outer函式最好在迴圈之外。我同意這個觀點,我同意。現在優化後的程式碼如下。
1 function addClickHandler(link, i){ 2 link.addEventListener('click', function(e){ 3 alert('You clicked on link ' + i) 4 }, false) 5 } 6 var links = documnet.getElementsByTagName('a') 7 for (var i = 0, len = links.length; i < len; i++) 8 addClickHandler(links[i], i)
這下程式碼看起來又幹淨又有效率了。
結論
你現在已經一點點的知道了閉包是如何工作的,並注意到迴圈裡的回撥函式的問題所在。如果你有任何疑問,請給我留言。