再谈JS闭包

闭包是外层函数作用域的引用

需求决定供给,闭包的出现是为了解决函数在定义作用域外执行时如何按照词法作用域查询变量的问题。

JS中的函数是第一公民,所以可以作为值被返回,从而在其他地方执行。当一个函数在定义作用域以外的地方执行时,如果按照正常的词法作用域,那么无法访问到定义时所在的外层函数作用域,所以闭包实际上补足了这一点。

对于一个有闭包的函数来说,访问顺序变为「本地作用域-闭包-外层作用域」,这里的闭包作为定义时外层函数作用域的引用,避免了作用域被垃圾回收,可以在任意位置进行访问。

function a() {
  let b = 1;
  function c() {
    b += 1;
    console.log(b);
  }
  return c;
}
let d = a();
debugger;
d();

image-20210816212028479

之所以一定是函数作用域,因为全局作用域最后回收,块作用域访问不到,两者不需要使用闭包来保留状态,只有函数作用域在执行完成后立刻销毁,所以需要闭包作为引用来保留状态。

疑问:当我在嵌套函数的外层添加了一个块作用域,闭包竟然没有了

function a() {
  { // 添加块
    let b = 1;
    function c() {
      b += 1;
      console.log(b);
    }
    return c;
  } // 添加块
}
let d = a();
debugger;
d();

image-20210816211841940

引用了自由变量就会创建闭包

闭包的创建取决于是否引用了外层函数作用域的变量(自由变量),和是否返回函数无关。

function a() {
  let b = 1;
  function c() {
    b += 1;
    console.log(b);
  }
  debugger;
  c();
}
a();

image-20210816213311316

闭包的查询顺序符合词法作用域

如果同时引用了多个外层作用域的变量,就会创建多个闭包。

function a() {
  let a_1 = 'a_1';
  function b() {
    let b_1 = 'b_1';
    function c() {
      console.log(a_1);
      console.log(b_1);
    }
    debugger;
    c();
  }
  b();
}
a();

其中,Closure(a)是函数b的闭包,Closure(b)是函数c的闭包,对于函数c而言按照"Local-Closure(b)-Closure(a)-Global"的顺序访问。
image-20210816214105584

总结

从上面的实验结果看,闭包实际上是嵌套函数访问词法作用域的一种特定模式,不论你是否返回内部函数,只要引用了外部函数作用域的变量,就会创建闭包,或者说内部函数是通过闭包的方式来访问外层变量。之所以会这样,还是因为函数是第一公民,存在定义和执行作用域不一致的情况,如果没有这一点,闭包也就不必存在了。

posted @ 2021-08-16 21:55  Peterer~王勇  阅读(46)  评论(0编辑  收藏  举报