动态语言的基石之函数闭包
闭包(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]
}
}
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]
}
}
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);
this.stage.addEventListener(MouseEvent.CLICK, sban.as3Expert.onMouseClick4);
3)类闭包
这是三类闭包中最简单的一种,也是最容易区分的一种,可能也是价值最低的一种,貌似根本不应该归为闭包类别。在AS3中,所有显式对象类型转换均是类闭包,如下:
// if obj is Closure which type anotation is Object
var obj1 :Closure = Closure(obj);
// 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";
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
onMouseClick2闭包->global
在这个链条内,根本不存在author变量,所以onMouseClick2的输出为undefined。
而onMouseClick1的作用域链为:
onMouseClick1->init->Closure->global
onMouseClick1->init->Closure->global
onMouseClick的作用域链为:
onMouseClick->Closure->global
onMouseClick->Closure->global
这两个作用域链均包括author变量,所以onMouseClick1与onMouseClick均不会输出undefined。
2008年5日
2021更新:AS3与js同属于ECMAScript语言,按理说在闭包概念上也应该是一致的。但从上文来说,AS3的闭包概念更宏大一些,貌似把类、方法、匿名函数都包括进去了。在js中,所谓闭包就是一个函数+不属于这个函数但被这个函数用到的上下文环境变量,两者绑在一起,闭合在一起,可以在程序中像一个独立的包裹一样传来传去。这篇小文是以前写AS3代码写的,关于三个闭包的分类叫法,并非我独创的,我记录当时Adobe文档或社区里也是这么分类的。俱往矣。