JS閉包文章--(翻譯)Callbacks in Loops

二胡嘈子發表於2014-02-26

原文地址: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)

這下程式碼看起來又幹淨又有效率了。

結論


你現在已經一點點的知道了閉包是如何工作的,並注意到迴圈裡的回撥函式的問題所在。如果你有任何疑問,請給我留言。

 

相關文章