Closure 与一级函数


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 关闭了个别的xfoo1foo2中的x彼此并不影响。

Closure 的应用很多,在 JavaScript 中常见用于对象私用(private)的模拟,以及命名空间的管理等,这之后还会再看到说明。


展开阅读全文