(AS3)在循环语句for、for each、while等语句中使用闭包需要注意的地方

遇到一个很有意思的BUG,是关于闭包的使用,大概简化后类似于以下代码:

 

复制代码
var arr:Array = [];
for (var i:int = 0; i < 2; i++) 
{
    arr[i] = function():void
    {
        trace(i);
    }
}
for each (var f:Function in arr) 
{
    f();
}
复制代码

 

 

猜一下上面代码的输出是什么?答案是:2 2

而事实上我期望的结果是:0 1

可为什么结果和我的期望相差这么大呢?

再看一段Lua代码:

 

复制代码
arr = {}

for i=0,1 do
    arr[#arr+1] = function()
        print(i)
    end
end

for _,v in ipairs(arr) do
    v()
end
复制代码

 

 

再猜一下,上面的答案?

这回的结果正好是我期望的结果,输出是:0 1

上面两段代码应该差不多是等价的,不过AS3中却并没有输出我期望的结果。

从我个人的期望上来说,我所理解的闭包,应该能在我定义function的时候就把所有的上下文保存好,这样也就能在调用的时候正确的取到upvalue,也就能正常的输出了。

对于这点,Lua的运行结果正是我需要的,可无奈的是我的主要开发语言是AS3,似乎AS3的闭包实现机制有些问题?循环语句中的 i 是一个引用,而再次调用 f 的时候,拿到的 i 还是原来的那个。

 

查了查资料,事实上早有人遇到过我类似的问题,传送门:http://stackoverflow.com/questions/422784/how-to-fix-closure-problem-in-actionscript-3-as3 

所以,也就有了以下的解决方法,看代码:

 

复制代码
var arr:Array = [];
for (var i:int = 0; i < 2; i++) 
{
    arr.push(test(i));
    function test(i:int):Function
    {
        return function():void
        {
            trace(i);
        }
    }
}
for each (var f:Function in arr) 
{
    f();
}
复制代码

 

 

这里做了件很巧妙的事情, test 方法返回了一个 function ,test 方法本身接受一个参数,而函数在传参过程中,类似 i:int 这样的基础类型数据是传值的,也就是说会拷贝一份 i 的复本出来,相同的数据类型还有其他的包括 Boolean 、Number 、String 、uint。

所以当调用 test 方法的时候实际上是保存了一个 i 的复本。然后 arr 再把 test 返回的方法塞进去,因此在调用 arr 中的方法的时候实际上调用的是 test 返回的那个匿名方法。

因此上面的输出就是我们期望的输出:0 1

 

这算不算一个BUG呢,不完整的闭包吗?

posted on 2013-09-13 12:59  风中雨2013  阅读(254)  评论(0)    收藏  举报

导航