# 闭包

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

主要是由 javascript 的两个特性产生的:

  • 词法作用域:内部函数可以访问函数外面的变量,是因为函数外面的变量保存在内部函数的词法作用域内,词法作用域的范围是由变量声明的位置决定,例:
function init() {
  var name = "Mozilla"; // name 是一个被 init 创建的局部变量

  // 在函数内部声明的函数,词法作用域就是外层函数
  function displayName() {
    alert(name); // 内层函数没有name变量,在外层函数中寻找,使用父函数中声明的变量
  }
  displayName();
}
init();
1
2
3
4
5
6
7
8
9
10
  • 函数作为值传递(first class):也就是函数即可以作为参数传入别的函数,也可以作为别的函数的返回值

在一个函数被作为返回值返回的时候,相当于返回了一个通道,这个通道可以访问函数的词法作用域,通过词法作用域也就可以访问到外部函数的变量,即使外部函数已经销毁,这样就产生了闭包,也就是能够读取其他函数内部变量的函数。

# 例子

var a = function() {
  var x = 1;
  var fn = function() {
    console.log(x);
  };
  fn();
  return fn;
};

var b = a();
b();
1
2
3
4
5
6
7
8
9
10
11

执行函数 a 然后把返回值赋值给了变量 b,函数 a 的返回值是一个函数 fn ,而函数作为返回值被返回的时候,实际上是返回的一个指向 fn 的指针,最后在执行 b 函数,也就是执行 fn 函数,fn 通过词法作用域可以访问 a 中的变量 x ,所以两次执行的结果都是显示 x 的值。

# 特点

  1. 可以使得函数内部变量只能被访问,无法被修改,访问途径只能通过函数内部提供的接口
  2. 由于闭包产生的引用,可以使得变量不会被自动回收

大部分应用都是基于上面的特点

# 应用场景

  • 匿名自执行函数:函数只执行一次,执行完毕立即释放资源,不会造成全局变量污染,例如 ui 初始化
  • 结果缓存:对于某些很耗时的函数,可以通过闭包将计算结果缓存,再次调用函数的时候,如果有缓存就使用缓存,没有在重新计算
  • 封装
  • 实现类和继承
  • nodejs 模块化

NodeJS 会给每个文件包上这样一层函数,引入模块使用 require,导出使用 exports,而那些文件中定义的变量也将留在这个闭包中,不会污染到其他地方

(funciton(exports, require, module, __filename, __dirname) {
	/* 自己写的代码  */
})(/* NodeJS 全局变量参数 */);
1
2
3