本文为历史博客迁移

首先应该先弄清楚闭包是什么,感觉这个词从字面意思上进行切入会越看越浑。

我只能强行理解为某某某明明已经合了,却又含着什么东西。诶?感觉好像有内味儿了。闭包不正是函数在其返回以后(理论上来说已经闭合)还能访问到函数内部的变量(包含对函数内部作用域的引用)么。

词法作用域

那么什么是词法作用域呢,就是根据声明变量的位置来确定变量可以发挥作用的域。

比如一个变量声明在全局,那它在全局作用域就是有作用的,可以被访问也可以被修改的,而操作它的时候,就要找到它的主人,也就是全局对象。

又比如一个变量声明在一个函数内,那它可以作用的域(范围)就是这个函数内。

特殊一点的是函数内嵌套函数,由于内部的函数是在外部函数的作用域内的,所以内部函数可以访问到外部函数中的变量,例如:

function outer() {
  var a = 2;
  function inner() {
    console.log(a);
  }
  inner();
}
outer();
// 2

其实这里面是存在一个过程的,正如js拥有原型链一样,作用域也存在一条链。

inner被调用的时候要访问一个叫a的变量,在inner作用域没找到,就会循着其作用域链向上找,它的上一层是outer作用域,然后在outer作用域找到了a,于是打印出来。

闭包

那么闭包的存在导致了一个什么事呢,它好像打破了作用域的规律。例如:

function outer() {
  var a = 2;
  function inner() {
    console.log(a);
  }
  return inner;
}
var inner = outer();
inner();
// 2

outer先被声明,其内部声明了一个变量a和一个函数inner

这两个东西理论上来说只属于 outer 函数内部(也就是仅供内部员工使用),像是私有的。

但是函数有权利定义自己的返回值,而函数这个东西在js中又被认为是“一等公民”,也就是说它和变量什么的是平起平坐的。

函数可以返回一个变量,当然也可以返回一个函数,同时函数可以接受变量作为参数,也可以接受函数作为参数。

所以这里outer就把 inner 函数返回了,重点是 inner 做了什么事呢,它里面在访问不属于自己却属于 outer 作用域的变量 a,这么一来这个 return inner 仿佛就在 outer 内部和其外部之间打了一个秘密通道。

导致结果有两个:

  1. outer 作用域内的变量被泄露到作用域外部;
  2. outer 作为一个函数,在 return 后理应释放占有的内存,也就是其内部声明的变量回被回收。

但这样一来,由于outer返回的是一个函数inner,而inner还在引用这个a,导致 a 无法被回收。更重要的是,最终在全局作用域调用inner时真的访问到了a,但a明明不存在于全局作用域。

按照作用域链的规则,当访问一个变量时,如果在当前作用域访问不到,会向上找。但此时已经是全局作用域,如果找不到就应该返回 undefined 了。但这里却得到 2,可见父作用域(全局作用域)访问了子作用域(outer)的变量,即是由于闭包的存在打破了阴阳两界(嵌套作用域之间的关系)。

代码仓库见: https://github.com/barnett617/codehub/blob/main/front-end/src/3-closure/index.md