闭包

150 阅读2分钟

什么是闭包?

MDN 是这样说的:

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。

说人话就是:

如果一个内层函数访问到其外层函数的作用域,那么这个函数加这个变量就叫做闭包

可以简单将闭包理解为“定义在一个函数内部的函数,可以读取其外部函数内部的变量”。

闭包最大的特点就是能保存它出生的环境。

for(var i = 0; i < 5; i++) {
    !function(i) {
        setTimeout(function () {
            console.log(i);
        });
    }(i)
}
// 0
// 1
// 2
// 3
// 4

上例通过闭包使函数继续访问它定义时的作用域。而这个新生成的作用域将每一次循环的当前 i 值单独保存了下来。

闭包的用途是什么?

  1. 读取外层函数内部的变量

    function f1() {
      var n = 999;
      function f2() {
        console.log(n);
      }
      return f2;
    }
    
    var result = f1();
    result(); // 999
    

由于 f2 在 f1 内部,因此可以读取 f1 内部的变量。

此时我们把 f2 作为返回值,就可以在 f1 外部读取它的内部变量了。

  1. 让变量始终保持在内存中,每次调用都是继承上一次

    function createIncrementor(start) {
      return function () {
        return start++;
      };
    }
    
    var inc = createIncrementor(5);
    
    inc() // 5
    inc() // 6
    inc() // 7
    

通过闭包,把createIncrementor函数内部的star变量保留下来,每次调用函数都是在上一次调用的基础上进行计算。

  1. 模拟对象的私有属性和私有方法

    function Person(name) {
      var _age;
      function setAge(n) {
        _age = n;
      }
      function getAge() {
        return _age;
      }
    
      return {
        name: name,
        getAge: getAge,
        setAge: setAge
      };
    }
    
    var p1 = Person('张三');
    p1.setAge(25);
    p1.getAge() // 25
    

函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。

闭包的缺点是什么?

闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

因为外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。使用不当容易造成内存泄露。