function foo(){
var local = 'private variable';
return function bar(){
return local;
}
}
var getLocalVariable = foo();
getLocalVariable() // private variable
其中最流行的闭包类型是广为人知的模块模式。它允许你模拟公共的,私有的和特权成员:
复制代码 代码如下:
var Module = (function(){
var privateProperty = 'foo';
function privateMethod(args){
//do something
}
return {
publicProperty: "",
publicMethod: function(args){
//do something
},
privilegedMethod: function(args){
privateMethod(args);
}
}
})();
模块实际上有些类似于单例,在末尾添加一对括号,当解释器解释完后立即执行(立即执行函数)。闭包执行上下位的外部唯一可用的成员是返回对象中公用的方法和属性(例如Module.publicMethod)。然而,所有的私有属性和方法在整个程序的生命周期中都将存在,由于(闭包)使执行上下文收到保护,和变量的交互要通过公用的方法。
另一种类型的闭包叫做立即调用函数表达式(immediately-invoked function expression IIFE),无非是一个在window上下文中的自调用匿名函数(self-invoked anonymous function)。
复制代码 代码如下:
function(window){
var a = 'foo', b = 'bar';
function private(){
// do something
}
window.Module = {
public: function(){
// do something
}
};
})(this);
对保护全局命名空间,这种表达式非常有用,所有在函数体内声明的变量都是局部变量,并通过闭包在整个运行环境保持存在。这种封装源代码的方式对程序和框架都是非常流行的,通常暴露单一全局接口与外界交互。
Call 和 Apply
这两个简单的方法,内建在所有的函数中,允许在自定义上下文中执行函数。call 函数需要参数列表而 apply 函数允许你传递参数为数组:
复制代码 代码如下:
function user(first, last, age){
// do something
}
user.call(window, 'John', 'Doe', 30);
user.apply(window, ['John', 'Doe', 30]);
执行的结果是相同的,user 函数在window上下文上被调用,并提供了相同的三个参数。
ECMAScript 5 (ES5)引入了Function.prototype.bind方法来控制上下文,它返回一个新函数,这函数(的上下文)被永久绑定到bind方法的第一个参数,无论函数被如何调用。它通过闭包修正函数的上下文,下面是为不支持的浏览器提供的方案:
复制代码 代码如下:
if(!('bind' in Function.prototype)){
Function.prototype.bind = function(){
var fn = this, context = arguments[0], args = Array.prototype.slice.call(arguments, 1);
return function(){
return fn.apply(context, args);
}
}
}
它常用在上下文丢失:面向对象和事件处理。这点有必要的因为 节点的addEventListener 方法总保持函数执行的上下文为事件处理被绑定的节点,这点很重要。然而如果你使用高级面向对象技术并且需要维护回调函数的上下文是方法的实例,你必须手动调整上下文。这就是bind 带来的方便:
复制代码 代码如下:
function MyClass(){
this.element = document.createElement('div');
this.element.addEventListener('click', this.onClick.bind(this), false);
}
MyClass.prototype.onClick = function(e){
// do something
};
当回顾bind函数的源代码,你可能注意到下面这一行相对简单的代码,调用Array的一个方法:
复制代码 代码如下:
Array.prototype.slice.call(arguments, 1);
有趣的是,这里需要注意的是arguments对象实际上并不是一个数组,然而它经常被描述为类数组(array-like)对象,很向 nodelist(document.getElementsByTagName()方法返回的结果)。他们包含lenght属性,值能够被索引,但他们仍然不是数组,由于他们不支持原生的数组方法,比如slice和push。然而,由于他们有和数组类似的行为,数组的方法能被调用和劫持。如果你想这样,在类数组的上下文中执行数组方法,可参照上面的例子。
这种调用其他对象方法的技术也被应用到面向对象中,当在javascript中模仿经典继承(类继承):
复制代码 代码如下: