概念
闭包(closure)是一个拥有任意变量以及绑定这些变量的环境(environment)的表达式(一般来说是就是function)
A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
在作用域内且在function定义时被访问的变量,那么这个变量就一直能够被那个function访问。
variables that are in scope and accessed from a function declaration will stay accessible by that function.
下面这个例子就是闭包,displayName
函数能够访问到不在其代码块里的name变量。
function init() {
var name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init();
函数的作用域 functional scoping
一个变量的作用域是以其所在的源代码的位置来定义的,嵌套在里面的function可以访问到声明在外层作用域的变量
The scope of a variable is defined by its location within the source code, and nested functions have access to variables declared in their outer scope.
还是拿刚才那个例子来说,displayName
函数是嵌套在init
函数里的,所以它能够访问到init
函数里的变量
function init() {
var name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init();
闭包的组成
先看一下这个例子:
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
按照java或C++的经验,局部变量name
的生命周期在函数的执行后就结束了,所以会推断name
在makeFunc()
访问后应该就访问不到了。
然而事实恰恰相反,唯一的解释就是myFunc
是一个闭包(closure)。
闭包由两部分组成:
function
创建该function的环境(创建闭包时,作用域内的所有局部变量)
对应到上面的这个例子里:
function:
displayName
环境:
name="Mozilla"
再看一个例子:
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
alert(add5(2)); // 7
alert(add10(2)); // 12
这个例子说明闭包的function可以是相同的,但是环境可以是不同的,因此就会有不同的结果。
归纳
因此可以将闭包归纳为:
定义时,确定可访问变量
执行时,确定变量的值
常见错误
下面这段代码实际上执行的时候并不是alert 0,1,2,3,4,而是alert 5次5。
这是为什么?因为i变量在for循环后变成了5,而在执行的时候我们才会确定闭包里i的值,在定义的时候不会记住i的值是什么的。
var funcs = [];
for(var i=0; i < 5; i++) {
funcs[i] = function() { alert(i); }
}
for(var j=0; j < funcs.length; j++) {
funcs[j]();
}
正确的写法是:
var funcs = [];
function makeFunc(x) {
return function() { alert(x); }
}
for(var i=0; i < 5; i++) {
funcs[i] = makeFunc(i)
}
for(var j=0; j < funcs.length; j++) {
funcs[j]();
}
闭包实践
函数工厂
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
私有变量
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
alert(Counter.value()); /* Alerts 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alerts 2 */
Counter.decrement();
alert(Counter.value()); /* Alerts 1 */
在这个例子里:
外界不能访问: privateCounter,changeBy
外界间接访问: increment,decrement,value
私有变量+函数工厂
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */
Counter1和Counter2绑定的环境相互独立。
性能问题
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}
执行一次,就会重新构造两个函数。
正确的做法应该是:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype = {
getName: function() {
return this.name;
},
getMessage: function() {
return this.message;
}
};
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。