Skip to content

🔰 动态语言的基石之函数闭包

🕒 Published at:

动态语言的基石之函数闭包

闭包(Closure)是函数(或方法)及其执行环境的组合体,它不仅包括函数(或方法)本身,也包括函数(或方法)运行时的上下文词汇环境。闭包是所有动态语言的基石,闭包实现了函数(或方法)可以作为参数传递给函数(或方法)。

1,用一个代码实验例示闭包概念

在AS3中,共种三种闭包:

1)函数闭包(Function Closure)

2)方法闭包(Method Closure)

3)类闭包(Class Closure)

js
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] 。

js
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]
  }
}

对于位于子包下的函数,可以这样直接使用:

js
this.stage.addEventListener(MouseEvent.CLICK, sban.as3Expert.onMouseClick4);
this.stage.addEventListener(MouseEvent.CLICK, sban.as3Expert.onMouseClick4);

3)类闭包

这是三类闭包中最简单的一种,也是最容易区分的一种,可能也是价值最低的一种,貌似根本不应该归为闭包类别。在AS3中,所有显式对象类型转换均是类闭包,如下:

js
// 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。

js
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文档或社区里也是这么分类的。俱往矣。