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