2010-06-01 2 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, которое было передано, а не к i, определенному в add_the_handlers. Возвращаемой функции присваивается onclick.

+0

Смотрите вопросы маркированные в: http://stackoverflow.com/questions/tagged/javascript+closures+loops – CMS

+0

Вы также можно поиграть с демо-версией http://jsbin.com/sezisalulede/1/edit?html,js,output –

ответ

20

Я думаю, что это очень распространенный источник путаницы для новичков в JavaScript. Во-первых, я хотел бы предложить проверить следующую статью Mozilla Dev для краткого введения на тему закрытия и лексической области видимости:

Начнем с плохой:

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

+0

@Matrym: Да, должен быть аргумент, который браузер переходит к функции обратного вызова при поднятии события onclick. Просмотрите [эту статью об ошибке] (http://www.quirksmode.org/js/events_access.html) о том, как это обрабатывается в разных браузерах. –

+0

Что произойдет, если мы не будем использовать переменную? Проходим ли мы, чтобы мы могли цеплять вещи? – Matrym

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 из-за их общей родительской области.

В хорошем примере используется анонимная функция, которая выполняется сразу же. Эта анонимная функция ссылается на ту же самую переменную i, что и в плохом примере, но поскольку она выполнена и снабжена i в качестве ее первого параметра, значение i присваивается локальной переменной, называемой ... eh? ... i, в точности - таким образом, переписывая ту, которая определена в области родителя.

Давайте перепишем хороший пример, чтобы сделать все это ясно:

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); 
    } 
}; 

Здесь мы заменили i в возвращаемой функции обработчика событий с newvar и он все еще работает, потому что newvar является только то, что можно было ожидать - это новая переменная, унаследованная от области анонимной функции.

Удачи, выясняя это.

Смежные вопросы