前车之鉴,闭包怎么深度理解 ?

254 阅读3分钟

0、此处为本篇最重要的部分

闭包:私有状态,可和函数联系起来,形成黑盒调用

最重要的部分:JavaScript 中的每个函数都维护对其外部词法环境的引用。此引用用于配置调用函数时创建的执行上下文。此引用使函数内部的代码能够“查看”函数外部声明的变量,而不管函数何时何地被调用

此时引出一个新的内容:作用域链

如果一个函数被一个函数调用,而该函数又被另一个函数调用,那么就会创建一个指向外部词法环境的引用链。这个链称为作用域链。

1、再谈谈概念

t0:内部 return 后的函数 可以使用 外层作用域的变量和函数调用,但外部不能反过来!
    因为 外部调用函数时,不能看到内层的 闭包部分,被视为一块不可见的‘状态框’,而这个状态框为一个黑盒部分 

1、相信这个概念你肯定多次看到,并且在实际工作中使用,vue date(){} 这是个典型的闭包,相信你早就注意到了
2、闭包是一个函数,和对该函数外部作用域的引用(词法环境)
   那么此处词法环境又是什么呢?
       词法环境是每个执行上下文(堆栈帧)的一部分,是标识符(即局部变量名称)和值之间的映射(简单理解为一个执行环境)
3、还有一个有意思的事情 
JavaScript 中的函数可以像变量(一等函数)一样传递,这意味着这些功能和状态的配对可以在您的程序中传递
4、如果没有闭包,函数调用直接将变得麻烦而且混乱 
5、如果您希望函数始终可以访问私有状态,则可以使用闭包
6、可多次访问,在另一个函数中声明一个函数,那么外部函数的局部变量在从它返回后仍然可以访问

2、举几个例子

// 第一个简单例子
// 闭包维护对原始变量本身的引用
    function foo() {
      let x = 42;
      let inner = function () {
        console.log(x);
      };
      x = x + 1;
      return inner;
    }
    var f = foo();
    f(); // logs 43

// 第二个例子
// 每次调用 createObject 时,都会创建一个新的执行上下文(堆栈帧)和一个全新的变量 x,并创建一组新的函数
function createObject() {
      let x = 42;
      return {
        log() {
          console.log(x);
        },
        increment() {
          x++;
        },
        update(value) {
          x = value;
        },
      };
    }

    const o = createObject();
    o.increment();
    o.log(); // 43
    o.update(5);
    o.log(); // 5
    const p = createObject();
    p.log(); // 42


// 第三个例子
// 每次循环时,都会创建一个新函数inner,它关闭i。但是因为 var i 被提升到循环之外,所有这些内部函数都关闭了同一个变量
function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

3、值得注意的事情

1JS中创建函数就会形成一个闭包
    声明函数时会创建一个闭包;这个闭包用于在调用函数时配置执行上下文
    每次调用函数时都会创建一组新的局部变量
2、从另一个函数内部返回一个函数 这是最常用的闭包     
3、每当您在函数内使用 eval() 时,都会使用闭包。你 eval 的文本可以引用函数的局部变量,在非严格模式下,你甚至可以使用 eval('var foo = …') 创建新的局部变量(用的少)
4、当您在函数内使用 new Function(...)(Function 构造函数)时,它不会关闭其词法环境:而是关闭全局上下文。新函数不能引用外部函数的局部变量
5JavaScript 中的闭包就像在函数声明点保留对作用域的引用(而不是副本),而后者又保留对其外部作用域的引用,依此类推,一直到顶部的全局对象作用域链。