2010-06-01 45 views
10

我在閱讀「Javascript:The Good Parts」,我完全被這裏發生的事情困惑不解。更詳細和/或簡化的解釋將不勝感激。閉包:逐行解釋「Javascript:Good Parts」的例子?

// BAD EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the wrong way. 
// When you click on a node, an alert box is supposed to display the ordinal of the node. 
// But it always displays the number of nodes instead. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (e) { 
      alert(i); 
     } 
    } 
}; 

// END BAD EXAMPLE 

add_the_handlers功能旨在給每個處理器的唯一編號(i)中。因爲處理函數被綁定到變量i,不是變量i在功能產生的時間值失敗:

// BETTER EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the right way. 
// When you click on a node, an alert box will display the ordinal of the node. 

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

現在,而不是到的onclick分配功能,我們定義一個函數,立即調用它,傳入i。該函數將返回一個事件處理函數,該函數綁定到傳入的i的值,而不是add_the_handlers中定義的i。該返回的函數被分配給onclick。

+0

見標記的問題:http://stackoverflow.com/questions/tagged/javascript+closures+loops – CMS 2010-06-01 06:52:55

+0

你也可以玩現場演示http://jsbin.com/sezisalulede/1/edit?html,js,output – 2014-09-15 18:09:07

回答

20

我認爲這是JavaScript新手混淆的一個常見原因。首先,我建議檢查出的關閉和詞法範圍的話題下面的Mozilla開發文章簡介:

讓我們先從壞一個:

var add_the_handlers = function (nodes) { 
// Variable i is declared in the local scope of the add_the_handlers() 
// function. 
    var i; 

// Nothing special here. A normal for loop. 
    for (i = 0; i < nodes.length; i += 1) { 

// Now we are going to assign an anonymous function to the onclick property. 
     nodes[i].onclick = function (e) { 

// The problem here is that this anonymous function has become a closure. It 
// will be sharing the same local variable environment as the add_the_handlers() 
// function. Therefore when the callback is called, the i variable will contain 
// the last value it had when add_the_handlers() last returned. 
      alert(i); 
     } 
    } 

// The for loop ends, and i === nodes.length. The add_the_handlers() maintains 
// the value of i even after it returns. This is why when the callback 
// function is invoked, it will always alert the value of nodes.length. 
}; 

我們可以通過更多關閉來解決這個問題,正如克羅克福德在「良好榜樣」中所建議的那樣。閉包是一種特殊的對象,它結合了兩件事:函數和創建該函數的環境。在JavaScript中,封閉的環境包括任何局部變量是在範圍內的時候,封閉創建的:

// Now we are creating an anonymous closure that creates its own local 
// environment. I renamed the parameter variable x to make it more clear. 
nodes[i].onclick = function (x) { 

    // Variable x will be initialized when this function is called. 

    // Return the event callback function. 
    return function (e) { 
     // We use the local variable from the closure environment, and not the 
     // one held in the scope of the outer function add_the_handlers(). 
     alert(x); 
    }; 
}(i); // We invoke the function immediately to initialize its internal 
     // environment that will be captured in the closure, and to receive 
     // the callback function which we need to assign to the onclick. 

而不是讓所有共享一個單一的環境回調,封閉函數創建一個每個人的新環境。我們也可以使用函數工廠創建一個封閉,如下面的例子:

function makeOnClickCallback (x) { 
    return function (e) { 
     alert(x); 
    }; 
} 

for (i = 0; i < nodes.length; i += 1) { 
    nodes[i].onclick = makeOnClickCallback(i); 
} 
+0

一個有點相關的問題。函數(e)中的「e」是什麼意思,並且可以用任何var替換它?我曾經認爲這意味着事件,但現在我感到困惑。 – Matrym 2010-06-01 08:02:37

+0

@Matrym:是的,應該是瀏覽器在onclick事件引發時傳遞給回調函數的參數。查看[這個quirksmode文章](http://www.quirksmode.org/js/events_access.html)瞭解如何在不同的瀏覽器中處理這個問題。 – 2010-06-01 08:15:28

+0

如果我們不使用變量會發生什麼?我們是否會通過這樣的連鎖? – Matrym 2010-06-01 08:30:46

3

這完全是關閉。在第一個例子中,對於每個點擊事件處理程序,「i」將等於「nodes.length」,因爲它使用創建事件處理程序的循環中的「i」。到事件處理程序被調用時,循環將結束,所以「i」將等於「nodes.length」。

在第二個例子中,「i」是一個參數(因此是一個局部變量)。事件處理程序將使用局部變量「i」的值(參數)。

0

它與關閉有關。

當你在惡劣的例子做的事,

當您單擊的每個節點,您將得到最新的I值(即你有3個節點,無論你點擊你會得到2哪個節點)。因爲您的警報(i)綁定到變量i的引用,而不是綁定到事件處理程序時的i值。

這樣做的更好的例子方式,你將它綁定到我現在正在迭代的東西上,所以點擊節點1會給你0,節點2會給你1,節點3會給你2

基本上,你正在評估我是什麼時候立即調用它的行}(i),它傳遞給了參數e,它現在保存了我在那個時刻的值。

Btw ...我認爲在更好的示例部分有一個錯字...它應該是警報(e)而不是警報(i)。

2

在這兩個示例中,任何傳遞的節點都有一個綁定到它的onclick事件處理程序(就像<img src="..." onclick="myhandler()"/>,畢竟這是不好的做法)。

不同之處在於,在每個閉包(即事件處理程序函數)的錯誤示例中,由於它們的公共父範圍引用了完全相同的i變量。

這個很好的例子使用了一個可以立即執行的匿名函數。這個匿名函數引用了與錯誤示例BUT完全相同的i變量,因爲它被執行並提供了i作爲其第一個參數,i的值被分配給了一個名爲...... eh的局部變量。 ... i,exactely - 因此覆蓋父母的範圍中定義的那個。

讓我們重寫好榜樣,使這一切清楚:

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (newvar) { 
      return function (e) { 
       alert(nevar); 
      }; 
     }(i); 
    } 
}; 

在這裏,我們取代了返回的事件處理函數inewvar,它仍然有效,因爲newvar是你所期望的正是 - 一個從匿名函數的作用域繼承的新變量。

祝你好運。