2

理解 lua 的 for 中的闭包,及其与 js 的闭包的比较

 3 years ago
source link: https://blog.henix.info/blog/lua-js-closure/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

理解 lua 的 for 中的闭包,及其与 js 的闭包的比较

最后更新日期:2013-04-02

  先看一段 js :取名 addEventListener 是因为这种写法在为一组 DOM 对象添加事件时很常见,也就是,事件响应函数用到了该对象的序号,也就是这里的循环变量 i 。一个常见的场景是为一个 tab 控件的标签头添加鼠标点击事件。

function addEventListener() {
    var ret = [];
    for (var i = 1; i <= 10; i++) {
        ret.push(function() {
            return i;
        });
    }
    return ret;
}

var funcs = addEventListener();
for (var i = 0; i < funcs.length; i++) {
    console.log(funcs[i]());
}

  结果是喜闻乐见的:10 个 11

  为什么呢?因为在闭包中使用的 free variable i ,只保存了一个引用,而不是 i 在运行中的值。

  再来看 lua 版:

function addEventListener()
    local ret = {}
    for i = 1, 10 do
        table.insert(ret, function()
            return i
        end)
    end
    return ret
end

local funcs = addEventListener()
for _, v in ipairs(funcs) do
    print(v())
end

  基本上是上面的 js 版的原样照抄。结果:1 2 3 4 5 6 7 8 9 10

  为什么 lua 与 js 版的结果不同?为什么 lua 版的结果更符合直觉?

  于是把 lua 版的 for 改成 while :

function addEventListener()
    local ret = {}
    local i = 1
    while i <= 10 do
        table.insert(ret, function()
            return i
        end)
        i = i + 1
    end
    return ret
end

local funcs = addEventListener()
for _, v in ipairs(funcs) do
    print(v())
end

  结果:10 个 11

  改成 while 了之后跟 js 一样,难道是 lua 对 for 做了什么特殊处理?

  我们把 lua 的 while 改成下面的样子:

function addEventListener()
    local ret = {}
    local j = 1
    while j <= 10 do
        local i = j
        table.insert(ret, function()
            return i
        end)
        j = j + 1
    end
    return ret
end

local funcs = addEventListener()
for _, v in ipairs(funcs) do
    print(v())
end

  创建了一个局部变量,结果:1 2 3 4 5 6 7 8 9 10

  可见,lua 的 for 就相当于 while ,只不过,对于循环计数器,lua 会每次都创建一个新的局部变量。

  那么,把上面的 lua 的 trick 运用到 js 中,是不是就可以解决 js 的问题了呢:

function addEventListener() {
    var ret = [];
    for (var i = 1; i <= 10; i++) {
        var j = i;
        ret.push(function() {
            return j;
        });
    }
    return ret;
}

var funcs = addEventListener();
for (var i = 0; i < funcs.length; i++) {
    console.log(funcs[i]());
}

  结果:10 个 10

  这是由于 js 没有块级作用域,只有函数级作用域,var j 相当于在函数的最开头声明了一个变量 j ,所以这个 trick 在 js 中没用。

  最终,我只能添加函数,创建一个作用域:

function addEventListener() {
    var ret = [];
    for (var i = 1; i <= 10; i++) {
        (function() {
            var j = i;
            ret.push(function() {
                return j;
            });
        })();
    }
    return ret;
}

var funcs = addEventListener();
for (var i = 0; i < funcs.length; i++) {
    console.log(funcs[i]());
}

  结果:1 2 3 4 5 6 7 8 9 10

  至此,终于把 lua 和 js 的闭包实现理清楚了,其实 js 和 lua 的闭包并没有太大的区别,区别只在 js 没有块级作用域上。

  P.S. 后来我才发现,python、php 和 js 一样,都只有函数级作用域,没有块级作用域。lua 可算是脚本语言中的一朵奇葩了。

评论邮箱 评论帮助

请按照如下格式发邮件:
[email protected]
[复制]
评论 / 回复内容,只支持纯文本

henix2015-10-26[回复]
ES6 的 let 可支持 for 的块级作用域:https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/
确实是这样,lua的for语句对于循环初始值,目标值,跨度都创建了块内局部变量进行存储。用一段小代码就可以看出来: local a = { 1, 2, 3 } for i = 1, #a do print(i) a = nil end print(a) 即使变量a在第一次进入循环就已经被置为nil了,可是循环依然会进入3次。 如果一定要把lua的for语句转换成等价的while语句的话,lua的官网给出了做法: 比如 for v = e1, e2, e3 do xxx end 转成 do local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3) if not(step > 0 and var <= limit) or (step <= 0 and var >= limit) do while (step > 0 and var <= limit) or (step <= 0 and var >= limit) do local v = var xxx var = var + step end end

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK