匿名函数
匿名函数
一、递归
递归函数是在一个函数通过名字调用自身的情况下构成的。
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。