JavaScript函数中的闭包

JavaScript 中的闭包通常被视作 JavaScript 的高级特性.但是我看了好几次文档也没有明白过来.也没有实际应用过.最近重新学习 JavaScript 打算好好学习下.

# 什么是闭包

闭包用通俗的说法来理解就是子函数可以使用父函数中的局部变量,这种行为叫做闭包.这个变量不是在这个代码块内或者全局上下文中定义的,而是 在定义代码块的环境中定义的(局部变量).一个经典的例子就是:

function a() {
  var i = 0;
  function b() {
    alert(++i);
  }
  return b;
}
var c = a();
c();
1
2
3
4
5
6
7
8
9

函数 b 嵌套在函数 a 内部,函数 a 返回函数 b.这样在执行了 var c = a() 之后,变量 c 变指向了函数 a.再指向 c()就会弹窗显示 i 的值.这段代码就创建了一个 闭包.因为函数 a 外的变量 c 引用了函数 a 内的函数 b.就是说,当函数 a 的内部函数 b 被函数 a 外的一个变量引用的时候,就创建了一个闭包.

# 闭包的用途

  • 可以读取函数内部的变量
  • 这些变量的值始终保持在内存中
  • 模拟私有变量

上面的例子中,i 的值只有 b 函数可以读取.当你不断执行 c()的时候,发现 i 值不断增大,说明 i 没有被内存回收.

# 使用闭包构造模块

模块是一个提供接口却隐藏状态与实现的函数或对象.通过使用函数产生模块,我们几乎可以完全摈弃全局变量的使用. 举例来说,假定我们想要给 String 增加一个 deentityify 方法.它的任务是寻找字符串中的 HTML 字符实体并把它们替换为对应的字符. 这就需要在一个对象中保存字符实体的名字和它们对应的字符.但我们该在哪里保存这个对象呢?我们可以把它放到一个全局变量中, 但全局变量是魔鬼.我们可以把它定义在该函数的内部,但是那会带来运行时的损耗,因为每次执行该函数的时候该字面量都会被求值一次. 理想的方式就是使用闭包实现.

Function.prototype.method = function(name, func) {
  this.prototype[name] = func;
  return this;
};
String.method(
  'deentityify',
  (function() {
    //字符实体表,它映射字符实体打名字到对应的字符.
    var entity = {
      quot: '"',
      lt: '<',
      gt: '>'
    };
    return function() {
      //查找以'&'开头和';'结束打子字符串.如果找到就替换.
      return this.replace(/&([^&;]+);/g, function(a, b) {
        var r = entity[b];
        return typeof r === 'string' ? r : a;
      });
    };
  })()
);
cosole.log('&lt;&quot;&gt;'.deentityify());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

通过给 Function.prototype 增加一个 method 方法,我们下次给对象增加方法的时候就不必键入 prototype 这几个字符,省掉了麻烦. 请注意最后一行,我们用()运算法立刻调用我们刚刚构造出来的函数.这个调用所创建并返回的函数才是 deentityify 方法.
关于这个的用法请参见 wiki:IIFE (opens new window)

# 闭包的缺点

闭包会使局部变量始终存在,内存消耗很大.在 IE 浏览器中它会很容易导致泄露内存. 还有就是不要在循环中引用闭包.

# 参考资料