Понимание захвата переменной закрытием в Javascript/Node

Есть ли определенный источник при захвате переменной в Javascript, кроме стандартного (это боль, чтобы прочитать стандарт)?

В следующем коде i копируется по значению:

for (var i = 0; i < 10; i++)
{
    (function (i)
    {
        process.nextTick(function ()
        {
            console.log(i)
        })
    }) (i)
}

Таким образом, он печатает 1..10. process.nextTick является аналогом setTimeout(f,0) в node.

Но в следующем коде i, похоже, не копируется:

for (var i = 0; i < 10; i++)
{
        var j = i
        process.nextTick(function ()
        {
            console.log(j)
        })
}

Он печатает 9 10 раз. Зачем? Меня больше интересует ссылка/общая статья, чем объяснение этого конкретного случая захвата.

Ответы

Ответ 1

У меня нет удобной ссылки. Но в нижней строке: во-первых, вы явно передаете i анонимную функцию, которая создает новую область. Вы не создаете новую область для i или j во второй. Кроме того, JavaScript всегда захватывает переменные, а не значения. Таким образом, вы тоже сможете изменить меня.

Ключевое слово JavaScript var имеет область действия, а не область блока. Таким образом, цикл for не создает область действия.

В качестве примечания, нестандартное ключевое слово let имеет локальную область действия.

Ответ 2

Он копируется (или присваивается) во втором примере, он просто содержит только одну копию переменной j, и она будет иметь значение, которое она имела в последнем случае, которое будет равно 9 (последний оборот вашего петля). Для создания новой копии переменной для каждого оборота цикла for требуется новое замыкание функции. Второй пример имеет только одну переменную, которая является общей для всех оборотов вашего цикла for, поэтому он может иметь только одно значение.

Я не знаю окончательной записи по этой теме.

Переменные в javascript привязаны к уровню функции. В javascript отсутствует область охвата. Таким образом, если вам нужна новая версия переменной для каждого оборота цикла for, вам нужно использовать новую функцию (создание закрытия функции), чтобы каждый раз получать это новое значение через цикл for. Без закрытия функции одна переменная будет иметь только одно значение, которое будет общим для всех пользователей этой переменной.

Когда вы объявляете переменную типа var j = i; в каком-либо месте, отличном от начала функции, javascript поднимает определение до вершины функции, и ваш код становится эквивалентным этому:

var j;
for (var i = 0; i < 10; i++)
{
        j = i;
        process.nextTick(function ()
        {
            console.log(j)
        })
}

Это называется variable hoisting и это термин, который вы могли бы использовать Google, если хотите узнать больше об этом. Но дело в том, что существует только область функций, поэтому переменная, объявленная где угодно в функции, фактически объявляется один раз в верхней части функции, а затем назначается в любом месте функции.

Ответ 3

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

Итак, во втором примере вы создаете десять анонимных функций (в process.nextTick(function(){...})), которые заключают в себе переменную ji, которая всегда имеет то же значение при создании анонимной функции). Каждая из этих функций использует значение j в то время, когда внешний цикл for работает полностью, поэтому j=i=10 в момент вызова каждой из функций. То есть, сначала ваш цикл for работает полностью, тогда ваши анонимные функции запускаются и используют значение j, которое уже установлено в 10!

В вашем первом примере ситуация немного отличается. Обернув вызов process.nextTick(...) в свою собственную анонимную функцию и связав значение i с функционально-локальной областью, вызвав функцию-оболочку (и, кстати, затеняя старую переменную i в параметр функции i), вы фиксируете значение переменной i в этот момент, вместо сохранения закрытой ссылки на i, значение которой изменяется в приложении внутренних анонимных функций.

Чтобы прояснить ваш первый пример, попробуйте изменить анонимную функцию-обертку, чтобы использовать аргумент с именем x ((function (x) { process.nextTick(...); })(i)). Здесь мы ясно видим, что x принимает значение в i в момент вызова анонимной функции, чтобы получить каждое из значений в for-loop (1..10).