小心AS3 函数闭包的陷阱

By:www.ethankang.com Date:2009-04-26
public var array:Array = []

public function setup():void{
for(var i:int=0;i<5;i++){
var obj:Object = {}
obj.num = function():void{
this.n = i
trace(this.n)
}
array.push(obj)
}
}

public function run():void{
for(var j:int=0;j<5;j++){
array[j].num()
}
}

先调用 setup 方法,在 setup 方法的 for 语句中循环创建对象 obj,并为 obj 创建一个方法 num(), 该方法将当前循环的 index 赋值给 obj 的一个属性 n,并 trace 出 n 的值。将 for 循环生成的对象存到数组 array 中用于在 run 方法中取出。

调用 run 方法,取出 array 中的对象,并调用他们的 num() 方法。

猜猜输出的结果是什么?

咳咳…. 

结果是:

5 5 5 5 5

并不是期望中的(我期望中的):

0 1 2 3 4

为什么呢?

研究并实验了几下才发现,原来函数闭包虽然可以记录上下文环境的 snapshot ,但却是最近状态的 一个 snapshot ,比如上例在 for 循环中,虽然把循环的当前的 i 值赋给了 n ,然而 i 是属于 num() 函数之外的,是 snapshot 中的变量,所以它只记录最近的状态,也就是 for 循环的最后一次,i 等于 5  。输出的就都是 5 了。

按照这个原理,你也可以实验一下,把 i 的最后状态改成其他的值,比如

public function setup():void{
for(var i:int=0;i<5;i++){
var obj:Object = {}
obj.num = function():void{
this.n = i
trace(this.n)
}
array.push(obj)
}
i = 100
}

虽然在 for 循环结束之后才设置的值,不过 i 依然在那个 snapshot 的范围之内,所以输出将会是 5 个 100 。

既然知道了原理,问题解决起来就容易了。只要让 i 不在函数闭包的上下文环境的 snapshot 的范围里就好了。可以这样改写一下:

public var array:Array = []

public function setup():void{
for(var i:int=0;i<5;i++){
array.push(createObj(i))
}
}

public function run():void{
for(var j:int=0;j<5;j++){
array[j].num()
}
}

public function createObj(index:int):Object{
var obj:Object = {}
obj.num = function():void{
this.n = index
trace(this.n)
}
return obj
}

将创建函数闭包的部分拿到 createObj() 方法中,这样这个 snapshot 只是该函数的上下文环境,不会受到 i 的重复赋值的影响,输出结果也是预期的 0 1 2 3 4

参与讨论去: 艾睿(Airia) 交朋友去:友吧推荐文章去:网站贴吧
企业招聘

北京中视力天文化传媒有限公司

公司简介:北京中视力天文化传媒有限公司,是CCTV.com央视国际网络有限公司投资的互联网研发团队。我们是天使的使者。。。

招聘FlashAS程序员详情点击

Copyright 2007-2008 51AS.com Extended in kingcms 鲁ICP备06001158号