函数 函数内部能访问到的变量(也叫环境)的总和,就是一个闭包。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。

我们知道了闭包的定义,那么就从定义下手,开始学习什么是闭包。

作用域

每个函数都会默认创造一个作用域。作用域指的是变量存在的范围。

在 ES5 的规范中,JavaScript 只有两种作用域:
一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取,这种变量称为“全局变量”;

  1. var a = 1;
  2. function f() {
  3. console.log(a);
  4. }
  5. f()

另一种是函数作用域,变量只在函数内部存在,外部无法读取,这种变量称为“局部变量”。

  1. function f() {
  2. var a = 1;
  3. }
  4. console.log(a);
  5. f()

闭包

通过对比上面两段代码,我们可以看出内部的函数可以访问到外部函数的变量,我们再举一个例子。

  1. function f1() {
  2. var name = "名字"; // name 是一个被 f1 创建的局部变量
  3. function f2() { // f2() 是内部函数,一个闭包
  4. alert(name); // 使用了父函数中声明的变量
  5. }
  6. f2();
  7. }
  8. f1();
  • JavaScript中的函数会形成了闭包。

    闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。

  • 在上面的代码里,f1() 创建了一个局部变量 name 和一个名为 f2() 的函数。

  • f2() 是定义在 f1() 里的内部函数,并且仅在 f1() 函数体内可用。
  • f2() 没有自己的局部变量,但因为f2()可以访问到外部函数的变量,所以 f2() 可以使用父函数 f1() 中声明的变量 name
  • f2 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。
  • 这里的f2() 就是个闭包


既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,就可以在f1外部读取它的内部变量了。

  1. function f1() {
  2. var name = "名字";
  3. function f2() {
  4. alert(name);
  5. }
  6. return f2;
  7. }
  8. var f = f1();
  9. f();

作用

闭包可以读取函数内部的变量,换句话说,可以隐藏一个变量。

  1. function create_counter(initial) {
  2. var x = initial || 0;
  3. return {
  4. inc: function () {
  5. x += 1;
  6. return x;
  7. }
  8. }
  9. }

它用起来像这样:

  1. var c1 = create_counter();
  2. c1.inc(); // 1
  3. c1.inc(); // 2
  4. c1.inc(); // 3
  5. var c2 = create_counter(10);
  6. c2.inc(); // 11
  7. c2.inc(); // 12
  8. c2.inc(); // 13

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

闭包还可以把多参数的函数变成单参数的函数。
例如,要计算x可以用Math.pow(x, y)函数,不过考虑到经常计算x或x,可以利用闭包创建新的函数pow2pow3

  1. function make_pow(n) {
  2. return function (x) {
  3. return Math.pow(x, n);
  4. }
  5. }
  6. // 创建两个新函数:
  7. var pow2 = make_pow(2);
  8. var pow3 = make_pow(3);
  9. console.log(pow2(5)); // 25
  10. console.log(pow3(7)); // 343

缺点

  1. 闭包在处理速度和内存消耗方面对脚本性能具有负面影响。例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是说,对于每个对象的创建,方法都会被重新赋值)。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。