13

为什么这两段看似相同的代码在 Javascript 和 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()
4

1 回答 1

7

就像下面这样简单:

Lualocal变量的范围仅限于最近的do-end块,而使用声明的 JavaScript 变量的范围仅限于var最近的函数边界。闭包通过将它们放置在函数中自己的单独作用域中来克服这一点,从而解决作用域问题。

关于您local i, j在外部范围内的问题,Lua 中的 for 语句会创建块范围中使用的计数器的范围,即使外部范围中有变量声明。文档说(参考):

循环变量 v 是循环的局部变量;你不能在 for 结束或被破坏后使用它的值。如果您需要此值,请在中断或退出循环之前将其分配给另一个变量。

这意味着var初始化是在 for 循环范围内进行的,因此放在local i, j外部范围内无效。这可以在文档中给出的 Lua 的 for 语句中看到:

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 的 for 语句差别很大(参考):

IterationStatement : for (var VariableDeclarationListNoIn ; Expressionopt ; Expressionopt ) 语句

因为for循环的声明和任何普通的变量声明都是一样的,相当于把它放在循环之外,与Lua for循环的工作方式大不相同。下一版本的 ECMAScript (ES6) 计划引入let关键字,它在 for 循环中的含义与 Lua 的 for 循环的工作方式相似:

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
于 2013-10-08T00:06:21.470 回答