JavaScript 匿名函数

匿名函数就是没有名字的函数,有时候也称为拉姆达(lambda)函数。匿名函数是一种强大的使人难以置信的工具,其用途很是之多,来看看下面这个典型的函数声明:前端

function functionName(arg0, arg1, arg2) {
    //函数体
}

 既能够像上面这样声明函数,也能够像下面这样以函数表达式的形式定义函数:web

var functionName = function(arg0, arg1, arg2) {
    //函数体
};

 虽然这两个例子在逻辑上等价,但它们之间仍是存在一些区别。固然,函数声明与函数表达式之间的主要区别,就是前者会在代码执行之前被加载到做用域中,然后者则是在代码执行到哪一行的时候才会有定义。另外一个重要的区别是函数声明会给函数指定一个名字,而函数表达式则是建立一个匿名函数,而后将这个匿名函数赋给一个变量。换句话说,上面第二个例子建立了带有三个参数的匿名函数,而后把这个匿名函数赋给了变量functionName,可是,并无给匿名函数指定名字。数组

像下面这样写一个匿名函数也是能够的:安全

function(arg0, arg1, arg2) {
    //函数体
}

 这些代码彻底有效,但问题是,谁也不可能调用这个函数,由于没有指向这个函数的指针。不过,在将函数做为参数传入另外一个函数,或者从一个函数中返回另外一个函数时,一般都要使用以这种形式来定义匿名函数。下面是曾经使用过的一个createComparisonFunction()函数的例子:闭包

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;
        }
    };
}

 createComparisonFunction()就返回了一个匿名函数,返回的函数可能会被赋值给一个变量,或者以其余方式被调用,不过,在createComparisonFunction()函数内部,它是匿名的,在把函数当成值的状况下,均可以使用匿名函数。不过,这并非匿名函数的惟一用途。app


 

1 递归函数

递归函数是在一个函数经过名字调用自身的状况下构成的,以下所示:工具

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

这是一个经典的递归阶乘函数,虽然这个函数表面看来没什么问题,但下面的代码却可能致使它出错:this

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4)); //VM848:5 Uncaught TypeError: factorial is not a function(…)

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

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

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

 加黄色背景的代码显示,经过使用arguments.callee代替函数名,能够确保不管怎样调用函数都不会出现问题。所以,在编写递归函数时,使用arguments.callee总比使用函数名更保险。


 

2 闭包

有很多开发人员老是搞不清匿名函数和闭包这两个概念,所以常常混用。闭包是指有权访问另外一个函数做用域中的变量的函数建立闭包的常见方式,就是在一个函数内部建立另外一个函数,仍之前面的createComparisonFunction()函数为例,注意加黄色背景的代码:

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 (value2 > value1) {
        return 1;
    } else {
        return 0;
    }
}

var result = compare(5, 10); //-1

 以上代码先定义了compare()函数,而后又在全局做用域中调用了它,当第一次调用compare()时,会建立一个包含this、arguments、value1和value2的活动对象。全局执行环境的变量对象(this、compare和result)在compare()执行环境的做用域链中则处于第二位。下图展现了包含上述关系的compare()函数执行时的做用域链:

后台的每一个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对象,则只在函数执行的过程当中存在。在建立compare()函数时,会建立一个预先包含全局变量对象的做用域链,这个做用域链被保存在内部的[[Scope]](scope chain)属性中。当调用compare()函数时,会为函数建立一个执行环境,而后经过复制函数的[[Scope]](scope chain)属性中的对象构建起执行环境的做用域链。此后,又有一个活动对象(在此做为变量对象使用)被建立并被推入执行环境做用域链的前端。对于这个例子中的compare()函数的执行环境而言,其做用域链中包含两个对象,本地活动对象和全局活动对象。显然,做用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

不管何时在函数中访问一个变量时,就会从做用域链中搜索具备相应名字的变量,通常来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局做用域(全局执行环境的变量对象),可是闭包的状况又有所不一样。

但另外一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的做用域链中。所以,在createComparisonFunction()函数内部定义的匿名函数的做用域链中,实际上将会包含外部函数createComparisonFunction()的活动对象。下图展现了当下列代码执行时,包含函数与内部匿名函数的做用域链:

var compare = createComparisonFunction('name');
var result = compare({ name: 'Nicholas' }, { name: 'Greg' });

在匿名函数从createComparisonFunction()中被返回后,它的做用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。这样,匿名函数就能够访问在createComparisonFunction()中定义的全部变量。更为重要的是,createComparisonFunction()函数在执行完毕后,其活动对象也不会被销毁,由于匿名函数的做用域链仍然在引用这个活动对象。换句话说,当createComparisonFunction()函数返回后,其执行环境的做用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。例如:

//建立函数
var compareNames = createComparisonFunction('name');

//调用函数
var result = compareNames({ name: 'Nicholas' }, { name: 'Greg' });

//删除对匿名函数的引用(以便释放内存)
compareNames = null;

首先建立的比较函数被保存在变量compareNames中,而经过compareNames设置为等于null解除该函数的引用,就等于通知垃圾回收例程将其清除。随着匿名函数的做用域链被销毁,其余做用域(除了全局做用域)也均可以安全地销毁了。上图展现了调用compareNames()的过程当中产生的做用域链之间的关系。(因为闭包会携带包含它的函数的做用域,所以会比其它函数占用更多的内存。过分使用闭包可能会致使内存占用过多,咱们建议读者只在绝对必要时再考虑使用闭包。)

2.1 闭包与变量

做用域链的这种配置机制引出了一个值得注意的反作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可用清晰地说明这个问题:

function createFunctions() {
    var result = new Array();

    for (var i = 0; i < 10; i++) {
        result[i] = function() {
            return i;
        };

    }

    return result;
}

var funcs = createFunctions();

//每一个函数都输出10
for (var i = 0; i < funcs.length; i++) {
    document.write(funcs[i]() + '<br />');
}

 这个函数会返回一个函数数组,表面上看,彷佛每一个函数都应该返回本身的索引值,即位置0的函数返回0,位置1的函数返回1,以此类推。但实际上,每一个函数都返回10。由于每一个函数的做用域链中都保存着createFunctions()函数的活动对象。因此它们引用的都是同一个变量i当createFunctions()函数返回后,变量i的值是10,此时每一个函数都引用着保存变量i的同一个变量对象,因此在每一个函数内部i的值都是10,可是,咱们能够经过建立另外一个匿名函数让闭包的行为符合预期,以下所示:

function createFunctions() {
    var result = new Array();

    for (var i = 0; i < 10; i++) {
       result[i] = function(num) { return function() { return num; }; }(i);
    }

    return result;
}

var funcs = createFunctions();

//分别输出0,1,2,3...
for (var i = 0; i < funcs.length; i++) { document.write(funcs[i]() + '<br />') }

 在重写了前面的createFunctions()函数后,每一个函数就会返回各自不一样的索引值了。在这个版本中,咱们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将当即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值。在调用每一个匿名函数时,咱们传入了变量i。因为函数参数是按值传递的,因此就会将变量i的当前值复制给参数num,而在这个匿名函数内部,又建立并返回了一个访问num的闭包。这样一来,result数组中的每一个函数都有本身num变量的一个副本。所以就能够返回各自不一样的数值了。

2.2关于this对象

在闭包中使用this对象也可能会致使一些问题。咱们知道,this对象是在运行时基于函数的执行环境绑定的,在全局函数中,this等于window,而当函数被做为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具备全局性,所以其this对象一般指向window(固然,在经过call()或apply()改变函数执行环境的状况下,this就会指向其余对象),但有时候因为编写闭包的方式不一样,这一点可能不会那么明显。下面来看一个例子:

var name = 'The Window';

var object = {
    name: 'My Object',

    getNameFunc: function() {
        return function() {
            return this.name;
        };
    }
};

alert(object.getNameFunc()()); //"The Window"

以上代码先建立了一个全局变量name,又建立了一个包含name属性的对象。这个对象还包含一个方法——getNameFunc(),他返回一个匿名函数,而匿名函数又返回this.name,因为getNameFunc()返回一个函数,所以调用object.getNameFunc()()就会当即调用它返回的函数,结果就是返回一个字符串。然而,这个例子返回的字符串是"The Window",即全局变量name的值。为何匿名函数没有取得其包含做用域(或外部做用域)的this对象呢?

前面曾经提到过,每一个函数在调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止所以永远不可能直接访问外部函数中的这两个变量,不过,把外部做用域中的this对象保存在一个闭包可以访问到的变量里,就可让闭包访问该对象了,以下所示:

var name = 'The Window';

var object = {
    name: 'My Object',

    getNameFunc: function() {
        var that = this;
        return function() {
            return that.name;
        };
    }
};

alert(object.getNameFunc()()); //"My Object"

 代码中突出的行展现了这个例子与前一个例子之间的不一样之处,在定义匿名函数以前,咱们把this对象赋值给了一个名叫that的变量。而在定义了闭包以后,闭包也能够访问这个变量,由于它是咱们在包含函数中特地声明的一个变量。即便在函数返回以后,that也仍然引用着object,因此调用object.getNameFunc()就返回了"My Object"。(this和arguments也存在一样的问题,若是想访问做用域中的arguments对象,必须将对该对象的引用保存到另外一个闭包可以访问的变量中。)

2.3 内存泄漏

因为IE对JScript对象和COM对象使用不一样的垃圾收集例程,所以闭包在IE中会致使一些特殊的问题。具体来讲,若是闭包的做用域链中保存着一个HTML元素,那么就意味着该元素将没法被销毁。来看下面的例子:

function assignHandler() {
    var element = document.getElementById('someElement');
    element.onclick = function() {
        alert(element.id);
    };
}

 以上代码建立了一个做为element元素事件处理程序的闭包,而这个闭包则又建立了一个循环引用(事件将在后面篇章中讨论)。因为匿名函数保存了一个对assignHandler()的活动对象的引用,所以就会致使没法减小element的引用数。只要匿名函数存在,element的引用数至少是1,所以它占用的内存就永远不会被回收。不过,这个问题能够经过稍微改写一下代码来解决。以下所示:

function assignHandler() {
    var element = document.getElementById('someElement');
    var id = element.id;

    element.onclick = function() {
        alert(id);
    };

    element = null;
}

 在上面的代码中,经过把element.id的一个副本保存在一个变量中,而且在闭包中引用该变量消除了循环引用。但仅仅作到这一步,仍是不能解决内存泄漏的问题。必需要记住,闭包会引用包含函数的整个活动对象,而其中包含着element。即便闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用,所以,有必要把element变量设置为null,这样就能解除对DOM对象的引用,顺利地减小其引用数,确保正常回收其占用的内存。


 

3. 模仿块级做用域

如前所述,JavaScript没有块级做用域的概念,这意味着在块语句中定义的变量,其实是在包含函数中而非语句中建立的,来看下面的例子:

function outputNumbers(count) {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
    alert(i); //count
}

 这个函数中定义了一个for循环,而变量i的初始值被设置为0。在Java、C++等语言中,变量i只会在for循环的语句块中有定义,循环一旦结束,变量i就会被销毁。但是在JavaScript中,变量i是定义在outputNumbers()的活动对象中的,所以从它有定义开始,就能够在函数内部随处访问它。即便像下面这样错误地从新声明一个变量,也不会改变它的值

function outputNumbers(count) {
    for (var i = 0; i < count; i++) {
        alert(i);
    }
    var i; //从新声明变量
    alert(i); //count
}

 JavaScript历来不会告诉你是否屡次声明了同一个变量,遇到这种状况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。匿名函数能够用来模仿块级做用域并避免这个问题

用做块级做用域(一般称为私有做用域)的匿名函数的语法以下所示:

(function() {
    //这里是块级做用域
})();

 以上代码定义并当即调用了一个匿名函数,将函数声明包含在一对圆括号中,表示它其实是一个函数表达式。而紧随其后的另外一对圆括号会当即调用这个函数。若是有读者感受这种语法不太好理解,能够再看看下面的例子:

var count = 5;
outputNumbers(count);

 这里初始化了变量count,将其值设置为5,固然,这里的变量是没有必要的,由于能够把值直接传给函数,为了让代码更简洁,咱们在调用函数时用5来调用变量count,以下所示:

outputNumbers(5);

 这样作之因此可行,是由于变量只不过是值的另外一种表现形式,所以用实际的值替换变量没有问题,再看下面的例子:

var someFunction = function() {
    //这里是块级做用域
};
someFunction();

 这个例子先定义了一个函数,而后当即调用了它。定义函数的方式是建立一个匿名函数,并把匿名函数赋值给变量someFunction。而调用函数的方式是在函数名称后面添加一对圆括号,即someFunction()。经过前面的例子咱们知道,可用使用实际的值来取代变量count,那在这里是否是也能够用函数的值直接取代函数名呢?然而,下面的代码却会致使错误:

function() {
    //这里是块级做用域
}(); //出错

 

这段代码会致使语法错误,是由于JavaScript将function关键字看成一个函数声明的开始,而函数声明后面不能跟圆括号,然而,函数表达式的后面能够跟圆括号。要将函数声明转换成函数表达式,只要像下面这样给它加上一对圆括号便可:

(function() {
    //这里是块级做用域
})();

 不管在什么地方,只要临时须要一些变量,就可使用私有做用域,例如:

function outputNumbers(count) {
    (function() {
        for (var i = 0; i < count; i++) {
            alert(i);
        }
    })();
    alert(i); //致使一个错误
}

 在这个重写后的outputNumbers()函数中,咱们在for循环外部插入了一个私有做用域。在匿名函数中定义的任何变量,都会在执行结束时被销毁。所以,变量i只能在循环中使用,使用后即被销毁。而在私有做用域中可以访问变量count,是由于这个匿名函数是一个闭包,它可以访问包含做用域中的全部变量。

这种技术常常在全局做用域中被用在函数外部,从而限制向全局做用域中添加过多的变量和函数。通常来讲,咱们都应该尽可能少向全局做用域中添加变量和函数。在一个由不少开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易致使命名冲突,而经过建立私有做用域,每一个开发人员既可使用本身的变量,又不比担忧搞乱全局做用域。例如:

(function() {
    var now = new Date();
    if (now.getMonth() == 0 && now.getDate() == 1) {
        alert('Happy new year!');
    }
})();

 把上面这段代码放在全局做用域中,可用用来肯定哪一天是1月1日,若是到了这一天,就会向用户显示一条祝贺新年的消息。其中的变量now如今是匿名函数中的局部变量,而咱们没必要在全局做用域中建立它。(这种作法能够减小闭包占用的内存问题,由于没有指向匿名函数的引用,只要函数执行完毕,就能够当即销毁其做用域链了。


 

4 私有变量

严格来说,JavaScript中没有私有成员的概念,全部对象属性都是公有的,不过,却是有一个私有变量的概念。任何在函数中定义的变量,均可以认为是私有变量,由于不能在函数的外部访问这些变量,私有变量包括函数的参数、局部变量和在函数内部定义的其余函数。来看下面的例子:

function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}

 在这个函数内部,有三个私有变量:num1,num2,sum.在函数内部能够访问这几个变量,但在函数外部则访问不到它们。若是在这个函数内部建立一个闭包,那么闭包经过本身的做用域链也能够访问这些变量。而利用这一点,就能够建立用于访问私有变量的共有方法。

咱们把有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。有两种在对象上建立特权方法的方式,第一种是在构造函数中定义特权方法,基本模式以下:

function MyObject() {

    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

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

 这个模式在构造函数内部定义了全部私有变量和函数。而后,又继续建立了可以访问这些私有成员的特权方法。可以在构造函数中定义特权方法,是由于特权方法做为闭包有权访问在构造函数中定义的全部变量和函数。对这个例子而言,变量privateVariable和函数privateFunction()只能经过特权方法publicMethod()来访问。在建立MyObject的实例后,除了使用publicMethod()这一个途径外,没有任何方法能够直接访问privateVariable和privateFunction()。

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

function Person(name) {

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

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

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

以上代码的构造函数中定义了两个特权方法:getName()和setName()。这两个方法均可以在构造函数外部使用,并且都有权访问私有变量name,但在Person构造函数外部,没有任何办法访问name。因为这两个方法是在构造函数内部定义的,它们做为闭包可以经过做用域链访问name。私有变量name在Person的每个实例中都不相同,由于每次调用构造函数都会从新建立这两个方法。不过,在构造函数中定义特权方法也有一个缺点,那就是你必须使用构造函数模式来达到这个目的,构造函数模式的缺点是针对每一个实例都会建立一组新方法,而使用静态私有变量来实现特权方法就能够避免这个问题。

4.1 静态私有变量

经过在私有做用域中定义私有变量或函数,一样也能够建立特权方法,其基本模式以下:

(function() {

    //私有变量和私有函数
    var privateVariable = 10;

    function 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(value) {
        name = value;
    };
})();

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

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

 

这个例子中的Person构造函数与getName()和setName()方法同样,都有权访问私有变量name,在这种模式下,变量name就变成了一个静态的、由全部实例共享的属性,也就是说,在一个实例上调用setName()会影响全部实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是全部实例都会返回相同的值。

以这种方式建立静态私有变量会由于使用原型而增进代码复用,但每一个实例都没有本身的私有变量,究竟是使用实例变量、仍是静态私有变量,最终仍是要视你的具体需求而定。(多查找做用域链中的一个层次,就会在必定程度上影响查找速度。而这正是使用闭包和私有变量的一个显明的不足之处。)

4.2 模块模式

前面的模式是用于为自定义类型建立私有变量和特权方法的。而道格拉斯所说的模块模式(module pattern)则是为单例建立私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。按照惯例,JavaScript是以对象字面量的方式来建立单例对象的:

var singleton = {
    name: value,
    method: function() {
        //这里是方法的代码
    }
};

 模块模式经过为单例添加私有变量和特权方法可以使其获得加强,其语法形式以下:

var privateVariable = function() {

    //私有变量和私有函数
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    //特权/共有方法和属性
    return {
        publicProperty: true;
        publicMethod: function() {
            privateVariable++;
            return privateFunction();
        }
    };
}();

 这个模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,首先定义了私有变量和函数,而后,将一个对象字面量做为函数的值返回。返回的对象字面量中只包含能够公开的属性和方法。因为这个对象是在匿名函数内部定义的,所以它的公有方法有权访问私有变量和函数。从本质来说,这个对象字面量定义的是单例的公共接口。这种模式在须要对单例进行某些初始化,同时又须要维护其私有变量时是很是有用的。 例如:

function BaseComponent() {}

function OtherComponent() {}

var application = function() {

    //私有变量和函数
    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()); //2

 在web应用程序中,常常须要使用一个单例来管理应用程序级的信息,这个简单的例子建立了一个用于管理组件的application对象,在建立这个对象的历程中,首先声明了一个私有的components数组,并向数组中添加了一个BaseComponent的新实例(在这里不须要关心BaseComponent的代码,咱们只是用它来展现初始化操做)。而返回对象的getComponentCount()和registerComponent()方法,都是有权访问components的特权方法。前者只是返回已注册的组件数目,后者用于注册新组件。

简言之,若是必须建立一个对象并以某些数据对其进行初始化,同时还要公开一些可以访问这些私有数据的方法,那么就可使用模块模式,以这种模式建立的每一个单例都是Object的实例。由于最终要经过一个对象字面量来表示它。事实上,这也没有什么,毕竟,单例一般都是做为全局对象存在的。咱们没必要将它传递给一个函数,所以,也就没有必要使用instanceof操做符来检查其对象类型了。

4.3 加强的模块模式

有人进一步改进了模块模式,即在返回对象以前加入对其加强的代码。这种加强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以加强的状况。来看下面的例子:

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;
}();

若是前面演示模块模式的例子中的application对象必须是BaseComponent的实例,那么就可使用如下代码:

var application = function() {

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

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

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

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

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

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

在这个重写后的应用程序(applicaiton)单例中,首先也是像前面例子中同样定义了私有变量,主要的不一样之处在于命名变量app的建立过程。由于它必须是BaseComponent的实例,这个实例其实是 applicaiton对象的局部变量版。此后,咱们又为app对象添加了可以访问私有变量的公有方法。最后一步是返回app对象,结果仍然是将它赋值给全局变量applicaiton.

5 小结

匿名函数,也称为拉姆达函数,是一种使用JavaScript函数的强大方式,如下总结了匿名函数的特色:

a,任何函数表达式从技术上说都是匿名函数,由于没有引用它们的肯定的方式;

b,在没法肯定如何引用函数的状况下,递归函数就会变得比较复杂;

c,递归函数应该始终使用arguments.callee来递归地调用自身,不要使用函数名——函数名可嫩会发生变化;

当在函数内部定义了其余函数时,就建立了闭包。闭包有权访问包含函数内部的全部变量,原理以下:

a,在后台执行环境中,闭包的做用域链包含着它本身的做用域、包含函数的做用域和全局做用域;

b,一般,函数的做用域及其全部变量都会在函数执行结束后被销毁;

e,但时,当函数返回了一个闭包时,这个函数的做用域将会一直在内存中保存到闭包不存在为止,使用闭包能够在JavaScript中模仿块级做用域(JavaScript自己没有块级做用域的概念),要点以下:

a,建立并当即调用一个函数,这样既能够执行其中的代码,又不会在内存中留下对该函数的引用;

b,结果就是函数内部的全部变量都会被当即销毁——除非将这些变量赋值给了包含做用域(即外部做用域)中的变量。

闭包还能够用于在对象中建立私有变量,相关概念和要点以下:

a,即便JavaScript中没有正式的私有对象属性的概念,但可使用闭包来实现公有方法,而经过共有方法能够访问在包含做用域中定义的变量;

b,有权访问私有变量的公有方法叫作特权方法;

c,可用使用构造函数模式、原型模式来实现自定义类型的特权方法,也可使用模块模式、加强的模块模式来实现单例的特权方法;

JavaScript中的匿名函数和闭包都是很是有用的特性,利用它们能够实现不少功能,不过,由于建立闭包必须维护额外的做用域,因此过分使用它们可能会占用大量内存。

相关文章
相关标签/搜索