ActionScript 3.0 闭包及作用域

Posted on Aug 5, 2012

闭包作为动态语言的基石,在OO实现和框架构建上有着-十分重要的作用。但是闭包的定义比较抽象,不利于理解,闭包在各语言中的实现不尽相同,闭包的函数作用域也有区别。

参考整理了这篇文章(非原创),主要内容来自

什么是闭包

闭包的概念出现于60年代,最早实现闭包的程序语言是Scheme。关于闭包的定义,说法比较多。

  • Scheme语言设计者这样定义闭包:”we introduce the notion of a closure which is a data structure containing a lambda expression, and an environment to be used when that lambda expression is applied to arguments.”

  • 英文Wikipedia 则是解释为:”a closure (also lexical closure or function closure) is a function together with a referencing environment for the non-local variables of that function. “。

  • 中文维基百科:”在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。” 综合这些定义,可以认为闭包一种特殊的数据结构,不仅仅是函数,还包括与其相关的引用环境。闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。其中的约束是指一个变量的名字和其所代表的对象之间的联系。那么为什么要把引用环境与函数组合起来呢?这主要是因为在支持嵌套作用域的语言中,有时不能简单直接地确定函数的引用环境。这样的语言一般具有这样的特性:

  • 函数是一阶值(First-class value),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。

  • 函数可以嵌套定义,即在一个函数内部可以定义另一个函数。

闭包作用域

函数的范围不但决定了可以在程序中的什么位置调用函数,而且还决定了函数可以访问哪些定义。适用于变量标识符的作用域规则同样也适用于函数标识符。在全局作用域中声明的函数在整个代码中都可用。例如,ActionScript 3.0 包含可在代码中的任意位置使用的全局函数,如 isNaN() 和 parseInt()。嵌套函数(即在另一个函数中声明的函数)可以在声明它的函数中的任意位置上使用。

作用域链(The scope chain)

无论何时开始执行函数,都会创建许多对象和属性。首先,会创建一个称为激活对象 的特殊对象,该对象用于存储在函数体内声明的参数以及任何局部变量或函数。由于激活对象属于内部机制,因此您无法直接访问它。接着,会创建一个范围链,其中包含由 Flash Player 或 Adobe AIR 检查标识符声明的对象的有序列表。所执行的每个函数都有一个存储在内部属性中的作用域链。对于嵌套函数,范围链始于其自己的激活对象,后跟其父函数的激活对象。作用域链以这种方式延伸,直到到达全局对象。全局对象是在 ActionScript 程序开始时创建的,其中包含所有的全局变量和函数。

函数闭包(Function closures)

函数闭包 是一个对象,其中包含函数的快照及其”词汇环境”。函数的词汇环境包括函数范围链中的所有变量、属性、方法和对象以及它们的值。无论何时在对象或类之外的位置执行函数,都会创建函数闭包。函数闭包保留定义它们的作用域,这样,在将函数作为参数或返回值传递给另一个作用域时,会产生有趣的结果。

例如,下面的代码创建两个函数:foo()(返回一个用来计算矩形面积的嵌套函数 rectArea())和 bar()(调用 foo() 并将返回的函数闭包存储在名为 myProduct 的变量中)。即使 bar() 函数定义了自己的局部变量 x(值为 2),当调用函数闭包 myProduct() 时,该函数闭包仍保留在函数 foo() 中定义的变量 x(值为 40)。因此,bar() 函数会返回值 160,而不是 8。

	 function foo():Function 
	{ 
		var x:int = 40; 
		function rectArea(y:int):int // function closure defined 
		{ 
			return x * y 
		}  
		return rectArea; 
	} 
	function bar():void 
	{ 
		var x:int = 2; 
		var y:int = 4; 
		var myProduct:Function = foo(); 
		trace(myProduct(4)); // function closure called 
	} 
	bar(); // 160

绑定方法(Bound methods)

绑定方法有时也叫做闭包方法(Method Closure),就是从它的实例提取的方法。作为参数传递给函数的方法或作为值从函数返回的方法都是绑定方法。在 ActionScript 3.0 中,新增的绑定方法类似于闭包函数,其中保留了词汇环境,即使从其实例中提取出来也是如此。绑定方法与闭包函数之间的主要不同差别是,绑定函数的 this 引用保留到实现方法的实例的链接或绑定。换句话说,绑定方法中的 this 引用总是指向实现方法的原始对象。对于闭包函数,this 引用是通用的,这意味着调用函数时,该引用指向与函数关联的任何对象。 如果使用 this 关键字,了解绑定方法就很重要。重新调用 this 关键字可提供对方法父对象的引用。大多数 ActionScript 程序员都希望 this 关键字总是引用包含方法定义的对象或类。但是,如果不使用方法绑定,并不是总是做到这样。例如,在以前版本的 ActionScript 中,this 引用并不总是引用实现方法的实例。从 ActionScript 2.0 的实例中提取方法后,不但 this 引用不绑定到原始实例,而且实例类的成员变量和方法也不可用。在 ActionScript 3.0 中不存在这样的问题,这是因为将方法当作参数传递时会自动创建绑定方法。绑定方法用于确保 this 关键字总是引用在其中定义了方法的对象或类。 下面的代码定义了名为 ThisTest 的类,该类包含一个名为 foo() 的方法(该方法定义绑定方法)和一个名为 bar() 的方法(该方法返回绑定方法)。类外部的代码创建 ThisTest 类的实例,然后调用 bar() 方法,最后将返回值存储在名为 myFunc 的变量中。

	class ThisTest 
	{ 
		private var num:Number = 3; 
		function foo():void // bound method defined 
		{ 
			trace("foo's this: " + this); 
			trace("num: " + num); 
		} 
		function bar():Function 
		{ 
			return foo; // bound method returned 
		} 
	} 
 
	var myTest:ThisTest = new ThisTest(); 
	var myFunc:Function = myTest.bar(); 
	trace(this); // output: [object global] 
	myFunc(); 
	/* output:  
	foo's this: [object ThisTest] 
	output: num: 3 */

代码的最后两行表明:虽然前一行中的 this 引用指向全局对象,但绑定方法 foo() 中的 this 引用仍然指向 ThisTest 类的实例。另外,存储在 myFunc 变量中的绑定方法仍然可以访问 ThisTest 类的成员变量。如果以上代码在 ActionScript 2.0 中运行,this 引用会匹配,但 num 变量将为 undefined。

绑定方法最值得注意的一种情况是使用事件处理函数,因为 addEventListener() 方法要求将函数或方法作为参数来传递。有关详细信息,请参阅事件侦听器中定义为类方法的 Listener 函数。

方法的行为与函数闭包类似,因为方法也保留有关创建它们的词汇环境的信息。当方法提取自它的实例(这会创建绑定方法)时,此特征尤为突出。函数闭包与绑定方法之间的主要区别在于,绑定方法中 this 关键字的值始终引用它最初附加到的实例,而函数闭包中 this 关键字的值可以改变。


参考资料

  1. http://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)
  2. http://en.wikipedia.org/wiki/Closure_(computer_science)
  3. Scheme:A Interpreter for Extended Lambda Calculus
  4. http://zh.wikipedia.org/wiki/Scheme
  5. http://www.ibm.com/developerworks/cn/linux/l-cn-closure/
  6. http://www.ibm.com/developerworks/cn/java/j-cb01097.html
  7. http://help.adobe.com/zh_CN/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f54.html
  8. http://help.adobe.com/zh_CN/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f30.html
  9. http://as3.iteye.com/blog/868217
  10. http://uh.9ria.com/space-3875-do-blog-id-2115.html
  11. http://fallenlord.blogbus.com/logs/60123823.html
  12. http://args.cn/post/2009/03/as3-function-carefully-trap-closure/