Ответ 1
Если обратный вызов определен в той же области действия, цикл определен в (что часто бывает), тогда обратный вызов будет иметь доступ к индексной переменной. Оставляя в стороне данные NodeJS на мгновение, рассмотрим эту функцию:
function doSomething(callback) {
callback();
}
Эта функция принимает ссылку на функцию обратного вызова, и все, что она делает, вызывает ее. Не очень интересно.: -)
Теперь позвольте использовать это в цикле:
var index;
for (index = 0; index < 3; ++index) {
doSomething(function() {
console.log("index = " + index);
});
}
(В вычислительно-интенсивном коде — как серверный процесс – – – –
Теперь, когда мы запустим это, мы увидим ожидаемый результат:
index = 0
index = 1
index = 2
Наш обратный вызов смог получить доступ к index
, потому что обратный вызов является закрытием по данным в области, где он определен. (Не беспокойтесь о терминах "закрытие", закрытия не сложны.)
Причина, по которой я сказал, что, вероятно, лучше всего не делать этого выше в вычислительно-интенсивном производственном коде, заключается в том, что код создает функцию на каждой итерации (запрет на оптимизацию в компиляторе, а V8 очень умный, но оптимизирующий создание эти функции нетривиальны). Итак, вот немного переработанный пример:
var index;
for (index = 0; index < 3; ++index) {
doSomething(doSomethingCallback);
}
function doSomethingCallback() {
console.log("index = " + index);
}
Это может показаться немного удивительным, но оно по-прежнему работает одинаково и все равно имеет тот же результат, потому что doSomethingCallback
по-прежнему является закрытием над index
, поэтому он все еще видит значение index
от когда он вызвал. Но теперь есть только одна функция doSomethingCallback
, а не новая в каждом цикле.
Теперь давайте возьмем отрицательный пример, что работает не:
foo();
function foo() {
var index;
for (index = 0; index < 3; ++index) {
doSomething(myCallback);
}
}
function myCallback() {
console.log("index = " + index); // <== Error
}
Это не работает, потому что myCallback
не определен в той же области (или вложенной области), в которой index
находится в определенном порядке, и поэтому index
есть undefined внутри myCallback
.
Наконец, рассмотрим настройку обработчиков событий в цикле, потому что нужно быть осторожным с этим. Здесь мы немного погрузимся в NodeJS:
var spawn = require('child_process').spawn;
var commands = [
{cmd: 'ls', args: ['-lh', '/etc' ]},
{cmd: 'ls', args: ['-lh', '/usr' ]},
{cmd: 'ls', args: ['-lh', '/home']}
];
var index, command, child;
for (index = 0; index < commands.length; ++index) {
command = commands[index];
child = spawn(command.cmd, command.args);
child.on('exit', function() {
console.log("Process index " + index + " exited"); // <== WRONG
});
}
Похоже, что приведенное выше должно работать так же, как и в наших предыдущих циклах, но есть решающее различие. В наших предыдущих циклах обратный вызов вызывался немедленно, и поэтому он видел правильное значение index
, потому что index
еще не успел двигаться дальше. В приведенном выше, однако, мы собираемся прокрутить цикл до вызова callback. Результат? Мы видим, что
Process index 3 exited
Process index 3 exited
Process index 3 exited
Это важный момент. У закрытия нет копии данных, которые она закрывает, у нее есть живая ссылка на нее. Таким образом, к моменту завершения обратного вызова exit
для каждого из этих процессов цикл уже будет завершен, поэтому все три вызова показывают одно и то же значение index
(его значение на конец цикла).
Мы можем исправить это, если обратный вызов использует другую переменную, которая не изменится, например:
var spawn = require('child_process').spawn;
var commands = [
{cmd: 'ls', args: ['-lh', '/etc' ]},
{cmd: 'ls', args: ['-lh', '/usr' ]},
{cmd: 'ls', args: ['-lh', '/home']}
];
var index, command, child;
for (index = 0; index < commands.length; ++index) {
command = commands[index];
child = spawn(command.cmd, command.args);
child.on('exit', makeExitCallback(index));
}
function makeExitCallback(i) {
return function() {
console.log("Process index " + i + " exited");
};
}
Теперь мы выводим правильные значения (в любом порядке, когда процессы выходят):
Process index 1 exited
Process index 2 exited
Process index 0 exited
То, как это работает, заключается в том, что обратный вызов, который мы назначаем событию exit
, закрывается по аргументу i
в вызове, который мы делаем для makeExitCallback
. Первый обратный вызов, который makeExitCallback
создает и возвращает закрытие по значению i
для этого вызова makeExitCallback
, второй обратный вызов, который он создает, закрывается по значению i
для этого вызова makeExitCallback
(который отличается от i
значение для более раннего вызова) и т.д.
Если вы дадите статью, указанную выше, прочитайте, что должно быть более четким. Терминология в статье немного устарела (ECMAScript 5 использует обновленную терминологию), но концепции не изменились.