JS 闭包探析。What exactly the Closure is?

计算机术语

闭包:引用了自由变量的函数。这个被引用的自由变量和这个函数一同存在,即使已经离开了创造它的环境。

变量作用域

  • 全局
  • 局部

函数内部可直接读取全局变量

var n1 = 9;

function f1() {
  alert(n1);
}

f1();
9

因为变量声明在 Global,全局可见,它的子当然可以访问到

函数外部无法直接读取函数内部的局部变量

function f2() {
  var n2 = 99;
}

alert(n2);
n2 is not defined

因为函数是子,函数外部是父,父无法直接访问到子的局部变量

函数内部变量如果未用 var 声明则为全局变量

function f3() {
  n3 = 999;
}

f3();

alert(n3);

999

不管变量是在函数内还是函数外声明,如果没有使用 var 声明变量,就会是全局可见的

于是此时的 n3 是 Global.n3

从外部读取局部变量

~想起了 Java 的反射~

方法:在函数内部再定义一个函数

function f4() {
  var n4 = 9999;

  function f5() {
    alert(n4);
  }
}

解析:

f5 在 f4 内部,f4(父)的所有局部变量对 f5(子)可见 反之,f5(子)的局部变量对 f4(父)不可见

即“链式作用域”结构(chain scope):子对象会一级一级向上寻找所有父对象的变量

将 f5 作为返回值,即可在 f4 外部读取其内部变量

function f4() {
  var n4 = 9999;
  function f5() {
    alert(n4);
  }
  return f5;
}

var result = f4();
result(); // Bingo! 9999

引用了自由变量的函数(f5)和被引用的自由变量(n4)一同存在,即使自由变量离开了创造它的环境(f4)。

这个函数(f5)称为闭包

闭包

上面的 f5 函数就是闭包(能够读取其他函数内部变量的函数——函数内部的子函数)

闭包是连接函数外部和函数内部的桥梁

用途

  • 读取函数内部的变量
  • 让这些变量的值始终保持在内存中
function f6() {
  var n6 = 999999;
  nAdd6 = function () {
    n6 += 1;
  };
  function f7() {
    alert(n6);
  }
  return f7;
}
var result6 = f6();
result6();
nAdd6();
result6();
999999
1000000

相当于 f6 的局部变量 n6 一直保存在内存中,并没有在 f6 调用后被自动清除

nAdd 的值是一个匿名函数,也相当于一个闭包,使得在函数外部可以对函数内部的局部变量进行操作

注意点

  • 闭包导致函数中的变量保存在内存中,消耗内存大,易造成性能问题,IE 中可能导致内存泄露(解决:退出函数前将不使用的局部变量删除)
  • 会在父函数外部改变内部变量的值,若把父函数当作对象(object)使用,把闭包当作其公用方法,把内部变量当作私有属性,切忌随意改变父函数内部变量的值

实践

var name = "The Window";

var object = {
  name: "My Object",

  getNameFunc: function () {
    return function () {
      console.log(this);
      return this.name;
    };
  },
};

alert(object.getNameFunc()());
Window
The Window

没取到对象内部的变量,则 this 指向 window 中的变量 name(当前匿名函数的 this 即 Window)

var name = "The Window";

var object = {
  name: "My Object",

  getNameFunc: function () {
    var that = this;
    return function () {
      return that.name;
    };
  },
};

alert(object.getNameFunc()());

// My Object

剖析:

通过函数 getNameFunc 内部的匿名函数取得 object 内部变量 name

object.getNameFunc()()

this 即是调用 getNameFunc 的 object

把 object 赋值给 that

that.name 即是 My Object

that.name,因为当前匿名函数没有 that 变量,会去其父寻找,找到,发现 that 的值是 this,this 又表示当前正在调用函数的对象,即 object

参考: http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html