Javascript 和 Lua 闭包的区别

Posted

技术标签:

【中文标题】Javascript 和 Lua 闭包的区别【英文标题】:Difference in Closures between Javascript and Lua 【发布时间】:2013-10-14 17:33:21 【问题描述】:

为什么这两段看似相同的代码在 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 方面。

我试图在 my blog 上解释这一点,但我不确定我的解释是否正确,因此我们将不胜感激。

编辑

谢谢大家,现在我明白了。这个稍加修改的 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()

【问题讨论】:

【参考方案1】:

就这么简单:

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

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

循环变量 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 语句差异很大 (reference):

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

【讨论】:

但在这两种情况下,ij 都声明在最外层范围内(在 main 函数内),因此它们在两种语言中应该具有相同的范围。 @FlightOdyssey:对于 Lua 案例,情况并非如此。 Lua for 循环总是声明一个新变量,因此循环中的变量每次迭代都是一个新变量。你声明的local i 最终会被隐藏和忽略(如果你删除它,结果应该是一样的)。 @FlightOdyssey 一个好的 Lua IDE 会在大纲中显示变量阴影,在编辑器中显示未使用的变量和未使用的赋值以及在调试器中具有相同名称的多个局部变量(由于阴影)。 (抱歉,我不能向您推荐我建造的那个。)

以上是关于Javascript 和 Lua 闭包的区别的主要内容,如果未能解决你的问题,请参考以下文章

深入理解javascript原型和闭包(完结)

JavaScript 闭包和 JavaScript 的区别? [关闭]

lua 和 luajit的区别

JavaScript 闭包

深入理解Lua的闭包:概念和应用

我从来不理解JavaScript闭包,直到有人这样向我解释它...