Подтвердить что ты не робот

Разница в закрытии между Javascript и Lua

Почему эти два, казалось бы, идентичных фрагмента кода ведут себя по-разному в Javascript и Lua?

Lua:

function main()
    local printFunctions={}
    local i,j
    for i=1,10 do
        local printi = function()
            print(i)
        end
        printFunctions[i]=printi
    end
    for j=1,10 do
        printFunctions[j]()
    end
end
main()

JavaScript:

function main()
{
    var printFunctions=[]
    var i,j;
    for(i=0;i<10;i++)
    {
        var printi = function()
        {
            console.log(i);
        }
        printFunctions[i]=printi;
    }
    for(j=0;j<10;j++)
    {
        printFunctions[j]();
    }
}
main()

Пример в Lua печатает 0 1 2 3 4 5 6 7 8 9, но пример в Javascript печатает 10 10 10 10 10 10 10 10 10 10. Может ли кто-нибудь объяснить разницу между закрытиями в Javascript и Lua, которые заставляют это произойти? Я исхожу из фона Javascript, поэтому, пожалуйста, сосредоточьтесь на стороне Lua.

Я попытался объяснить это на моем блоге, но я не уверен, правильно ли объяснено, поэтому любое разъяснение будет оценено.

ИЗМЕНИТЬ

Спасибо всем, теперь я понимаю. Эта слегка измененная версия кода Lua печатает 10,10,10,10,10,10,10,10,10,10, как ожидалось

function main()
    local printFunctions={}
    local i,j,k
    for i=1,10 do
        k=i
        local printi = function()
            print(k)
        end
        printFunctions[i]=printi
    end
    for j=1,10 do
        printFunctions[j]()
    end
end

main()
4b9b3361

Ответ 1

Это так просто, как следующее:

Переменные Lua local привязаны только к ближайшему блоку do-end, а переменные JavaScript, объявленные с помощью var, привязаны к ближайшим границам функций. Замыкания преодолевают эту точку, помещая их в свою собственную область действия в функцию, решая проблему области.

Что касается вопроса о том, что local i, j находится во внешней области, оператор for в Lua создает область действия счетчика, используемого в области блока, даже если во внешней области есть объявление переменной. В документации указано (ссылка):

Переменная цикла v является локальной для цикла; вы не можете использовать его значение после конца или сломано. Если вам нужно это значение, назначьте его другой переменной перед тем, как выходить из цикла или выходить из него.

Это означает, что инициализация var сделана локальной для области цикла for, поэтому размещение local i, j во внешней области не влияет. Это можно увидеть в эквиваленте инструкции Lua для инструкции, приведенной в документации:

do
    local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
    if not (var and limit and step) then error() end
    while (step > 0 and var <= limit) or (step <= 0 and var >= limit) do
        local v = var
        block
        var = var + step
    end
end

Однако JavaScript для оператора значительно отличается (ссылка):

IterationStatement : for ( var VariableDeclarationListNoIn ; Expressionopt ; Expressionopt ) Statement

Поскольку объявление цикла for совпадает с объявлением любой обычной переменной, оно эквивалентно размещению его вне цикла, что значительно отличается от того, как работает цикл Lua for. Следующая версия ECMAScript (ES6) планирует ввести ключевое слово let, которое в цикле for будет иметь аналогичное значение для работы Lua for loop:

for (let i = 0; i < 10; ++i) setTimeout(function () { console.log(i); }, 9); // 0,1,2,3,4,5,6,7,8,9
for (var i = 0; i < 10; ++i) setTimeout(function () { console.log(i); }, 9); // 10,10,10,10,10,10,10,10,10,10