Закрытия: Линию за линией объяснение “JavaScript: Хорошие Части” пример?

Я читаю "JavaScript: Хорошие Части" и полностью экранированы тем, что действительно продолжается здесь. Более подробное и/или упрощенное объяснение значительно ценилось бы.

// 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.

11
задан Kevin Brown 20 February 2015 в 02:09
поделиться

4 ответа

Я думаю, что это очень частый источник путаницы для новичков в 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);
}
20
ответ дан 3 December 2019 в 04:12
поделиться

Это связано с закрытием.

Когда вы делаете то, что в плохом примере,

, когда вы щелкаете каждый узел, вы получаете последнее значение i (т.е. у вас есть 3 узла, независимо от того, какой узел вы щелкнете, вы получите 2). поскольку ваше предупреждение (i) привязано к ссылке на переменную i, а не к значению i в момент, когда оно было привязано в обработчике событий.

Делая это лучшим примером, вы привязываете его к тому, что i, как в момент, когда он был повторен, поэтому щелчок по узлу 1 даст вам 0, узел 2 даст вам 1, а узел 3 даст вам 2.

в основном вы оцениваете то, что есть i, сразу, когда оно вызывается в строке} (i), и оно передается в параметр e, который теперь содержит значение того, что i есть в данный момент времени.

Кстати ... Я думаю, что в лучшей части примера есть опечатка ... должно быть alert (e) вместо alert (i).

0
ответ дан 3 December 2019 в 04:12
поделиться

Все дело в закрытии. В первом примере «i» будет равно «nodes.length» для каждого обработчика событий щелчка, потому что он использует «i» из цикла, который создает обработчики событий. К моменту вызова обработчика событий цикл закончится, поэтому «i» будет равно «nodes.length».

Во втором примере «i» является параметром (то есть локальной переменной). Обработчики событий будут использовать значение локальной переменной «i» (параметр).

3
ответ дан 3 December 2019 в 04:12
поделиться

В обоих примерах любой передаваемый узел имеет привязанный к нему обработчик события onclick (так же, как , что, в конце концов, является плохой практикой).

Разница в том, что в плохом примере каждое закрытие (функции обработчика событий, то есть) ссылается на одну и ту же переменную 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 - это как раз то, что вы ожидали - новая переменная, унаследованная из области видимости анонимной функции.

Удачи в решении этой проблемы.

2
ответ дан 3 December 2019 в 04:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: