闭包

    注意到返回的函数在其定义内部引用了局部变量 arr ,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

    另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了 f() 才执行。我们来看一个例子:

    function count() {

    var arr = [];

    for (var i=1; i<=3; i++) {

    arr.push(function () {

    return i * i;

    });

    }

    return arr;

    }



    var results = count();

    var f1 = results[0];

    var f2 = results[1];

    var f3 = results[2];

    在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个 Array 中返回了。

    你可能认为调用 f1() f2() f3() 结果应该是 1 4 9 ,但实际结果是:

    f1(); // 16

    f2(); // 16

    f3(); // 16

    全部都是 16 !原因就在于返回的函数引用了变量 i ,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量 i 已经变成了 4 ,因此最终结果为 16

    返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

    如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

    function count() {

    var arr = [];

    for (var i=1; i<=3; i++) {

    arr.push((function (n) {

    return function () {

    return n * n;

    }

    })(i));

    }

    return arr;

    }



    var results = count();

    var f1 = results[0];

    var f2 = results[1];

    var f3 = results[2];



    f1(); // 1

    f2(); // 4

    f3(); // 9

    注意这里用了一个“创建一个匿名函数并立刻执行”的语法:

    (function (x) {

    return x * x;

    })(3); // 9

    理论上讲,创建一个匿名函数并立刻执行可以这么写:

    function (x) { return x * x } (3);

    但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:

    (function (x) { return x * x }) (3);

    通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:

    (function (x) {

    return x * x;

    })(3);

    说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?

    当然不是!闭包有非常强大的功能。举个栗子:

    在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用 private 修饰一个成员变量。

    在没有 class 机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:

    'use strict';



    function create_counter(initial) {

    var x = initial || 0;

    return {

    inc: function () {

    x += 1;

    return x;

    }

    }

    }

    它用起来像这样:

    var c1 = create_counter();

    c1.inc(); // 1

    c1.inc(); // 2

    c1.inc(); // 3



    var c2 = create_counter(10);

    c2.inc(); // 11

    c2.inc(); // 12

    c2.inc(); // 13

    在返回的对象中,实现了一个闭包,该闭包携带了局部变量 x ,并且,从外部代码根本无法访问到变量 x 。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。

    闭包还可以把多参数的函数变成单参数的函数。例如,要计算x<sup>y</sup>可以用 Math.pow(x, y) 函数,不过考虑到经常计算x<sup>2</sup>或x<sup>3</sup>,我们可以利用闭包创建新的函数 pow2 pow3

    function make_pow(n) {

    return function (x) {

    return Math.pow(x, n);

    }

    }



    // 创建两个新函数:

    var pow2 = make_pow(2);

    var pow3 = make_pow(3);



    pow2(5); // 25

    pow3(7); // 343