闭包(Closure)是函数(或方法)及其执行环境的组合体,它不仅包括函数(或方法)本身,也包括函数(或方法)运行时的上下文词汇环境。闭包是所有动态语言的基石,闭包实现了函数(或方法)可以作为参数传递给函数(或方法)。
1,用一个代码实验例示闭包概念
在AS3中,共种三种闭包:
1)函数闭包(Function Closure)
2)方法闭包(Method Closure)
3)类闭包(Class Closure)
public class Closure extends Sprite
{
public function Closure()
{
super();
init();
}
private var author :String = "sban";
private var onMouseClick2 :Function = function(e :MouseEvent) : void
{
trace("author:" + author, "this:" + this);//author:undefined this:[object global]
};
private function init() :void
{
var onMouseClick1 : Function = function(e :MouseEvent) : void
{
trace("author:" + author, "this:" + this);//author:sban this:[object global]
};
this.stage.addEventListener(MouseEvent.CLICK, onMouseClick2);
}
private function onMouseClick(e :MouseEvent) :void
{
trace("author:" + author, "this:" + this);//author:sban this:[object Closure]
}
}
在上例代码中,分别以onMouseClick,onMouseClick1,onMouseClick2为listener向stage添加click事件监听,trace结果如代码中注释。其中,onMouseClick是方法闭包,onMouseClick1与onMouseClick2是函数闭包。这里有一个问题,为什么在onMouseClick2中author的输出结果是undefined?
2,三类闭包的区分
在AS3中,任何一个函数(或方法)调用,至少会有一个this参数,这几乎是所有动态语言一惯的规则,不同的是,有的语言对程序员是可见的,如Python,有的则不可见,如AS3。
1) 方法闭包
所有类实例的方法,作为参数传递时,均是方法闭包,隐匿的第一个this参数永远是类实例本身,如上例代码中的onMouseClick便是方法闭包,所以它的this输出为[object Closure]。
2) 函数闭包
所有匿名方法(包括局部变量方法,类变量方法,见上),全局方法(包括位于根包下的全局方法,位于子包下的全局方法,见下)均是函数闭包,所有函数闭包的第一个函数如果是null,将被默认替换为Global对象,所以我们看到的输出均是[object global] 。
package
{
import flash.events.MouseEvent;
function onMouseClick3(e :MouseEvent) :void
{
trace("this:" + this);//this:[object global]
}
}
package sban.as3Expert
{
import flash.events.MouseEvent;
public function onMouseClick4(e :MouseEvent)
{
trace("this:" + this);//this:[object global]
}
}
对于位于子包下的函数,可以这样直接使用:
this.stage.addEventListener(MouseEvent.CLICK, sban.as3Expert.onMouseClick4);
3)类闭包
这是三类闭包中最简单的一种,也是最容易区分的一种,可能也是价值最低的一种,貌似根本不应该归为闭包类别。在AS3中,所有显式对象类型转换均是类闭包,如下:
//if obj is Closure which type anotation is Object
var obj1 :Closure = Closure(obj);
Closure在这里不是操作符,也不是别的什么东西,在这里应该把它理解为一个特殊的方法。这个方法第一个参数仍然为this,第二个参数是将被作类型转换的对象,在上例代码中为obj。
3,改变函数闭包的this参数的一种情况
在函数闭包中,this参数(null)通常被默默置换为global对象,在某些情况下,程序员可以传递真实的this参数进去,而不是null,从而避免被替换为global对象。
在Array的forEach, every, map, some, filter这些API中,第一个参数为函数对象,第二个参数为第一个参数的this对象,当程序员指定第二个参数时,便可以在第一个函数内访问this上下文环境的变量,如果不指定,便不能再其内使用this。
public function ArrayForEachThis()
{
super();
var arr :Array = [1,2,3];
arr.forEach(
function (item :int,index :int=-1,arr :Array = null) :void
{
trace(item, this.author)
}
//,this
);
}
private var author :String = "sban";
编码规范:在使用Array的forEach, every, map, some, filter这些API时,必须在第二个参数位传递this进去。
4,为什么在onMouseClick2中author的输出是undefined?
所有AS3程序员都应当知道,在AS3运行时,有一个作用域链,该作用域链自global始,在运行时变量首先从最近的链点查找,如果未找到,再向上查找,直到找到或到达global链点。
onMouseClick2函数的运行时作用域链为:
onMouseClick2闭包->global
在这个链条内,根本不存在author变量,所以onMouseClick2的输出为undefined。
而onMouseClick1的作用域链为:
onMouseClick1->init->Closure->global
onMouseClick的作用域链为:
onMouseClick->Closure->global
这两个作用域链均包括author变量,所以onMouseClick1与onMouseClick均不会输出undefined。
作用域链的节点包括闭包对象,但不仅包括它。
sban 2009/5/15 北京。转载请注明作者及出处,非商用。