AS3作用域规则静态作用域&闭包(closure)

  as3的作用域让人感觉有点乱,不知道改如何专业的解释,最近一直在看 python 源码分析,受了些启发。也许as3也遵守这种静态作用域规则,感觉所有的动态语言都有异曲同工之妙吧。

静态作用域(static scope,也叫lexical scope,字面作用域),是一种根据语言文本的位置确定变量引用的规则。我从wikipedia上找到一个解释:

With static scope, a variable always refers to its top-level environment. This is a property of the program text and unrelated to the runtime call stack. Because matching a variable to its binding only requires analysis of the program text, this type of scoping is sometimes also called lexical scoping. Static scope is standard in modern functional languages such as ML and Haskell because it allows the programmer to reason as if variable bindings are carried out by substitution. Static scoping also makes it much easier to make modular code and reason about it, since its binding structure can be understood in isolation. In contrast, dynamic scope forces the programmer to anticipate all possible dynamic contexts in which the module’s code may be invoked.

  它的意思是,某个变量到底是什么值,是由program text决定的,说白了,你写code的时候定义变量的位置在哪儿就哪儿。我举个例子说明一下:

代码
package {    
    
import flash.display.Sprite;     
    
public class scope_test extends Sprite    {
        
public function scope_test()        
        {        
            fee()();    
        }         
        
private var i:int = 1
        
private function fee():Function    
        {            
            var i:
int = 2;    
            var f:Function 
= function():void    
            {            
                trace(i)
// output 2    
            }        
                
return f;    
        }    
    }
}
  这个例子中,函数f在构造函数中执行,但是输出的i不是指向类变量i,而是函数fee里局部变量i。这就是静态作用域的结果,完全是根据文本位置决定的,而不管函数在哪里调用(哪怕在别的对象里)。

  有时候我想,这样的作用域是如何实现的呢。as3 和 flash player 本身没有开放,我在这里做的也只是猜想,像一些胡言乱语者一样拿一些理论往里套而已。要理解作用域规则就不得不提到“闭包”(closure)的概念。要知道在as3的世界里一切都是对象,方法也是对象。闭包一般就是针对方法对象的。方法内部的变量可以认为是方法对象的对象属性。

我在livedocs上搜到两个概念的解释,其中一个是作用域链(scope chain):

Any time a function begins execution, a number of objects and properties are created. First, a special object called an activation object is created that stores the parameters and any local variables or functions declared in the function body. You cannot access the activation object directly because it is an internal mechanism. Second, a scope chain is created that contains an ordered list of objects that Flash Player checks for identifier declarations. Every function that executes has a scope chain that is stored in an internal property. For a nested function, the scope chain starts with its own activation object, followed by its parent function’s activation object. The chain continues in this manner until it reaches the global object. The global object is created when an ActionScript program begins, and contains all global variables and functions.

  可以把作用域链看成一个链表,每个域是一个包含很多变量定义的节点,而头节点就是方法内部定义的变量的集合。作用域链的概念是用来解决变量查找的。试想如果方法有个变量i,在方法内部没有定义,这时候作用域链就会发挥作用,方法内部找不到是不,那从外面那层找,比如class,global等等,一层层向外查找知道找到。

还有一个重要概念就是方法闭包(function closure):

A function closure is an object that contains a snapshot of a function and its lexical environment. A function’s lexical environment includes all the variables, properties, methods, and objects in the function’s scope chain, along with their values. Function closures are created any time a function is executed apart from an object or a class. The fact that function closures retain the scope in which they were defined creates interesting results when a function is passed as an argument or a return value into a different scope.

  老外都喜欢直来直去哈,第一句就说明了问题,closure是一个object,其实我还没搞清楚这里的object是as概念上的,还是一个广泛的对象概念(我偏向后者)。这个closure object包含了function本身的快照,和lexical environment,即上面提到的作用域链。

  为了更好的理解“闭包”概念,我在这里对上面的closure object做了些猜测。首先要知道,对于计算机来讲,as3等高级语言是不存在的,因为它只懂二进制。as3是一种先编译后解释的语言(java/.net/python也是)。无论是flex还是fla,先编译到swf(它是一个二进制的可被flash player识别的结构),swf在flash player上运行,这个运行过程实际上应该是解释的,flash player实时的翻译到本机二进制代码运行。这里再废话一句,为什么讲flash是跨平台跨浏览器的,答案就是flash player,对于普通as3开发者来讲,把as3编译到swf就算任务完成,而swf是一个独立于任何软件的结构。

  现在再回到closure object。所以我对于livedocs上提到的closure object,认为它是一个二进制上的对象概念。我这里拿图片来表示一个closure struct机构。

closure object

null

  当一个方法执行的时候,as3会根据方法名找到这个方法的object,方法object和它的上下文环境会打包成一个closure object,然后将他们写入内存。所以在方法执行之前,所有的变量包括局部的全局的都已经可见了(注意可能还没有初始化),执行的时候想要查找变量就很容易了,顺着作用域链找好了。再拿上一篇的例子说明一下。

代码
package
{
    
import flash.display.Sprite;

    
public class scope_test extends Sprite
    {
        
public function scope_test()
        {
            fee()();
        }
        
private var i:int = 1;

        
private function fee():Function
        {

            var i:
int = 2;
            var f:Function 
= function():void
            {
                trace(i) 
// output 2      
            }
            
return f;
        }
    }
}

这里执行fee()()实际上就是执行f(),因为静态作用域规则as3根本不会管f这个function object在哪里执行的,它总是从字面上决定上下文环境。这里局部变量i和f就会被打包成一个closure object,所以trace(i)这句执行的时候,i就会顺着作用域链在外层作用域找到。

还有个例子:

代码

package
{
    
import flash.display.Sprite;

    
public class scope_test extends Sprite
    {
        
public function scope_test()
        {
            fee()
        }
        
private var i:int = 1;

        
private function fee():void
        {
            trace(i); 
//output 0  
       var i:int = 2;  
        }
    }
}

当执行fee()的时候由于closure object的存在,局部变量i已经在activation object内声明了(它在作用域链的头部,没有必要往外查找)。而执行到trace(i)的时候,由于i的赋值语句还没有被压入方法堆栈,于是i的值是0而已。

 
原文地址:https://www.cnblogs.com/sevenyuan/p/1615478.html