匿名函数

匿名函数

一、递归
递归函数是在一个函数通过名字调用自身的情况下构成的。

function factorial(num){
    if(num <= 1){
        return 1;
    } else {
        return num * factorial(num - 1);
    }
}

这是一个经典的递归阶乘函数。虽然这样看起来没有什么问题,但如果使用以下的方法调用的话:

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));        //error

以上代码先把facotrial()函数保存在变量anotherFactorial中,然后将factorial变量设置为null,结果指向原始函数的引用只剩下了一个,但在接下来调用anotherFactorial()时,由于必须执行factorial(),而factorial已经不再是函数,所以就会导致错误。在这种情况下,使用arguments.callee可以解决这个问题。

arguments.callee 是一个指向正在执行的函数的指针,因此可以用它实现对函数的递归调用。

function factorial(num){
    if(num <= 1){
        return 1;
    } else{
        return num * arguments.callee(num - 1);
    }
}

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));    //24

 

二、闭包

闭包:是指有权访问另一个函数作用域中的变量的函数。

创建闭包的常见方式,就是在一个函数内部创建另一个函数.

function createComparisonFunction(propertyName){
    return function(object1, object2){
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];

        if(value1 < value2){
            return -1;
        } else if (value1 > value2){
            return 1;
        } else {
            return 0;
        }
    };
}

在上面这个例子中,匿名函数中调用了外部函数中的变量propertyName。即使这个内部函数被返回了,而且是在其他地方被调用了,但它让然可以访问标量propertyName。之所以还能够访问这个变量,是因为内部函数的作用域链中包含createComparisonFunction()的作用域。

当某个函数第一次被调用时,会创建一个执行环境(execution context)及相应的作用域链,并把作用域链赋值给一个特殊的内部属性[Scope]。然后,使用this、arguments和其他命名参数的值来初始化函数的活动对象(activation object)。
但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链重点的全局执行环境。

在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。

function compare(value1, value2){
    if(value1 < value2){
        return -1;
    } else if (value1 > value2){
        return 1;
    } else {
        return 0;
    }
}

var result = compare(5, 10);

以上代码先定义了compare()函数,然后又在全局作用域中调用了它。当第一次调用compare()时,会创建一个包含this、arguments、value1和value2的活动对象。

全局执行环境的变量对象(包括this、result和compare)在compare()执行环境的作用域链中则处于第二位。

后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的剧本环境的变量对象,则只在函数执行的过程中存在。

在创建comapre()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的Scope属性中。当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的Scope属性中的对象构建起执行环境的作用域链。

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕之后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况有所不同。

在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。因此,在createComparisonFunction()函数内部定义的匿名函数的作用域链中,实际上会包含外部函数createComparisonFunction()的活动对象。

在匿名函数从createComparisonFunction()中返回后,它的作用域链被初始化为包含createComparsionFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()中定义的所有变量。

更重要的是,createComparisonFunction()函数在执行完毕之后,其活动对象也不会被销毁,因为匿名函数的作用域链仍在引用这个活动对象。换句话说,当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,createComparisonFunciton()的活动对象才会被销毁。

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,建议只在绝对必要的时候再考虑使用闭包。


三、私有变量
任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数内部访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而利用这一点,就可以创建用于访问私有变量的公有方法。

有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。

在对象上创建特权方法的方式有两种:
1、在构造函数中定义特权方法。

function MyObject(){
    //函数的私有变量
    var privateVariable =  10;

    functiono privateFunction(){
        return false;
    }

    //特权方法
    this.publicMehtod = function(){
        privateVariable++;
        return privateFunction();
    };
}

利用私有和特权成员,可以隐藏那些不应该被直接修改的数据:

funciton Person(name){
    
    this.getName = function(){
        return name;
    };

    this.setName = function(value){
        name = value;
    };
}

通过以上方法(在构造函数中定义特权方法)有一个缺点,就是必须使用构造函数模式来达到这个目的。而构造函数模式的缺点是每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题。

静态私有变量

(function(){
    
    //私有变量和私有函数
    var privateVaribale = 10;

    functiono privateFunction() {
        return false;
    }

    //构造函数
    MyObject = function(){

    };

    //公有/特权方法
    MyObject.prototype.publicMethod = function(){
        privateVariable ++;
        return privateFunction();
    };
})();

这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有函数,然后又定义了构造函数及公有方法。

公有方法是在原型上定义的,这一点体现了典型的原型模式。

需要注意的是,这个模式在定义构造函数的时候并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因,声明MyObject时也没有使用var关键字。

初始化未经声明的变量,总会创建一个全局变量。因此,MyObject就成了一个全局变量,能够在私有作用域之外被访问到。


这个模式与在构造函数中定义特权方法的主要区别,就在于私有变量和函数时由实例共享的。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用。

(function(){
    
    var name = "";

    Person = function(value){
        name = value;
    };

    Person.prototype.getName = function(){
        return name;
    };

    Person.prototype.setName = function( valeu ){
        name = value;
    };
})();

var person = new Person("Nicholas");
alert(person.getName());        //"Hicholas"
person.setName("Greg");            
alert(person.getName());        //"Greg"

var person2 = new Person("Michael");
alert(person.getName());        //"Michael"
alert(person2.getName());        //"Michael"

注意:多查找作用域链中的一个层次,就会在一定程度上影响查找速度。而这正是使用闭包和私有变量的一个明显的不足之处。

2、模块模式

模块模式(module pattern)是为单例创建私有不变量和特权方法。

按照惯例,JavaScript是以对象字面量的方式来创建单例对象的。

var singleton = {
    name : value,
    method : function(){
        //logic
    }
};

模块模式通过为单例添加私有变量和特权方法使其能够得到增强。

var singleton = function(){
    
    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    //特权/公有方法和属性
    return {
        publicProperty : true,

        publicMethod : function(){
            privateVariable ++;
            return privateFunction();
        }
    };
}();

================================================================================

function BaseComponent(){
    
}

function OtherComponent(){
    
}

var application = fucntion(){
    
    //私有变量和函数
    var components = new Array();

    //初始化
    components.push(new BaseComponent());

    //公有
    return {
        getComponentCount : function(){
            return components.length;
        },

        registerComponent : function(component){
            if(typeof component == "object"){
                components.push(component);
            }
        }
    };
}();

application.registerComponent(new OtherComponent());
alert(application.getComponentCount());

 

注意:对于函数表达式来说,定义并立即调用匿名函数不需要将函数封装在一对括号中。当然,对于函数声明比如是封装在一对括号中。


增强的模块模式

var singleton = function(){
    
    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    //创建对象
    var object = new CustomType();

    //添加特权/公有属性和方法
    object.publicProperty = true;

    object.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };

    //返回这个对象
    return object;
}

================================================================================

var application = function(){
    
    //私有变量和函数
    var components = new Array();

    //初始化
    components.push( new BaseComponent());

    //创建application的一个局部副本
    var app = new BaseComponent();

    //公共接口
    app.geComonentCount = function(){
        return components.length;
    };

    app.registerComponent = function(component){
        if( typeof component == "object"){
            components.push(component);
        }
    };

    //返回这个副本
    return app;
}();

这个重写后的应用程序(application)单例中,首先也是像前面例子中一样定义了私有变量。主要的不同之处在于命名变量app的创建过程,因为它必须是BaseComponent的实例。这个实例实际上是application对象的局部变量版。

此后,又为app对象添加了能够访问私有变量的公有方法。最后返回app对象,结果仍然是将它复制给全局变量application。

posted @ 2013-02-21 14:11  向往天空的鱼  阅读(410)  评论(0编辑  收藏  举报