Closure 是拥有闲置变量(Free variable)的表达式。闲置变量真正扮演的角色依当时环境而定。支持 Closure 的程序语言通常具有一级函数(First-class function)。创建函数不等于创建 Closure 。如果函数的闲置变量与当时环境绑定,该函数才称为 Closure。
那么何为闲置变量?闲置变量是指对于被创建的函数而言,既非局部变量也非参数的变量。举个例子来说:
function doSome() {
var x = 10;
function f(y) {
return x + y;
}
return f;
}
var foo = doSome();
console.log(foo(20)); // 30
console.log(foo(30)); // 40
上面的函数doSome
中,函数f
创建了一个 Closure,如果你单看:
function f(y) {
return x + y;
}
看来起变量x
似乎没有定义。实际上,x
是从外部函数捕捉而来。 Closure 是个捕捉了外部函数变量(或使之继续存活)的函数。在上例中,函数f
创建了 Closure,因为它将变量x
关入(close)自己的范围,这也是 Closure 这个名称的由来。如果形成 Closure 的函数对象持续存活,被关闭的变量x
也会继续存活。就像是延续了变量x
的生命周期。
由于doSome
返回了函数对象并指定给foo
,就doSome
而言已经执行完毕。单看x
的话,理应x
已结束其生命周期,但由于doSome
中创建了 Closure 并返回,x
被关闭在 Closure 中,所以x
的生命周期就与 Closure 的生命周期相同了。如上例所示,调用foo(20)
结果就是10 + 20
(因为被闭关的x
值是 10),调用foo(30)
结果就是10 + 30
。
如果没有捕捉任何变量,那么就是单纯的(一级)函数而已。例如,在下面的例子中,函数f
没有形成 Closure,因为它没有捕捉外部任何变量:
function doSome() {
var x = 10;
function f(y) {
return y + 10;
}
return f;
}
注意! Closure 关闭的是变量,而不是变量所参考的值。下面这个范例可以证明:
function doSome() {
var x = 10;
function f(y) {
x = x + y;
return x;
}
return f;
}
var foo = doSome();
console.log(foo(20)); // 30
console.log(foo(30)); // 60
创建函数f
时绑定了x
变量,形成了一个 Closure,绑定的是x
变量,而不是数值 10(x
变量的值),因此,第一次调用foo(20)
后,x
的值成了10 + 20
,再次调用foo(30)
时,x
的值原本是 30,在x = x + y
之后,x
的值就成了30 + 30
。
由于 Closure 绑定的是变量,你才可以在 Closure 中改变了被绑定变量的值,以下是另一个例子:
var sum = 0;
[1, 2, 3, 4, 5].forEach(function(elem) {
sum += elem;
});
console.log(sum); // 15
你在 Closure 中改变了sum
的值,forEach
执行完之后,获取sum
的值也才会是 15。
你可能会有疑问的是,如果 Closure 关闭了某个变量,使得该变量的生命周期得以延长,那么这个会怎么样?
function doSome() {
var x = 10;
function f(y) {
x = x + y;
return x;
}
return f;
}
var foo1 = doSome();
var foo2 = doSome();
console.log(foo1(20)); // 30
console.log(foo2(20)); // 30
在这个范例中,doSome
被调用了两次,每次调用时其实都创建了个别的局部变量x
,而个别创建的 Closure 关闭了个别的x
。foo1
与foo2
中的x
彼此并不影响。
Closure 的应用很多,在 JavaScript 中常见用于对象私用(private)的模拟,以及命名空间的管理等,这之后还会再看到说明。