闭包

230 阅读3分钟

MDN:闭包是函数和声明该函数的词法环境的组合。

要想直接通过闭包的定义来理解这个功能十分困难,不如我们先来看看用它来解决什么问题,有怎样的用途?

提升变量作用域

在javascript语言中,存在一种“链式作用域”的语言环境。子环境会一级级向上寻找父环境的变量。父环境的所有变量,对子环境透明可见,反之则不成立。

    var a = 1;
    var fn = function () {
        var b = 2;
        function getVariable () {
            console.log('a and b:', a, b);
        };
        getVariable();
    };
    fn();
    // a and b: 1 2
    console.log('getB:', b);
    // ReferenceError: b is not defined

javascript存在两种作用域环境,一种是全局作用域,一种是函数级作用域。上面例子中可见三层作用域,分别是全局作用域,fn函数作用域,getVariable函数作用域,内层作用域可访问外层作用域变量,反之不可。

那么当我们在外层作用域想访问内层作用域定义的变量时,该怎么办呢?使用闭包便可以解决这一问题。

    var fn = function () {
        var b = 2;
        return function () {
            console.log('b:', b);
        };
    };
    var getB = fn();
    getB();
    // b: 2

在函数fn中返回一个函数,使用内层作用域可以访问其父作用域变量这一特点,来提升内层作用域的变量至外层。这样便达到了提升变量作用域的效果。

注意

    var fn = function () {
        var b = 2;
        return function () {
            console.log('b:', this.b);
        };
    };
    var getB = fn();
    getB();
    // b: undefined

这个例子可能并非如你所想可以得到b的值2,它输出了undefind。原因时fn所返回的是一个全新的function,已脱离了fn的上下文环境,this实际指向了全局对象window。

如此修改是否可以得到想要的答案:

    var fn = function () {
        var b = 2;
        function getB () {
            console.log('b:', this.b);
        }.bind(this);
        return getB;
    };
    var getB = fn();
    getB();
    // b: undefined

以上例子也未能得到预想的答案,原因是函数调用的上下文执行环境是全局对象window(非严格模式),所以并不能得到b的值2。

再次改造:

    var obj = {
        b : 2,
        getB: function () {
            return function () {
                return this.b
            }
        }
    };
    var res = obj.getB();
    console.log(res.call(obj));
    // 2

让变量值保持在内存中

function fn () {
    var a = 1;
    add = function () {
        a += 1;
    };
    function f1 () {
        console.log('a:', a);
    }
    return f1;
};

var res = fn();
res(); // a: 1
add();
res(); // a: 2

在上面例子中,将闭包函数f1赋给res变量,两次运行函数res,都可以得到函数fn中的局部变量a,说明变量a并没有在fn调用后从内存中被自动清除。
由于fn是f1的父函数,f1的执行依赖于fn,当闭包函数f1被赋值给变量res时,fn也始终存在于内存中,不会在调用后被回收。

使用闭包的注意点

  • 闭包会使得函数中的变量都被保存在内存中,滥用闭包很容易造成内存泄漏。因此,在退出函数之前,将不适用的局部变量全部删除。
  • 闭包的使用可能会在父函数外部暴露操作父函数内部变量的接口,从而导致内部变量的值更新。在不必要的情况下,尽量只对变量做读操作。