闭包,看这篇就够了!

闭包

首先借用阮老师对闭包(closure)的概念做出的定义:

closure.jpg

在《JavaScript高级程序设计(第3版)》中文版中[3],具体描述在第7章函数表达式第7.2节(页码为第178页):闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。

  2. 从实践角度:以下函数才算是闭包:

    1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
    2. 在代码中引用了自由变量

闭包是怎么保存数据作为缓存数据使用?

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是闭包的情况又有所不同,闭包是一个函数捕获它被定义时所在的环境,这个环境在该函数的引用被销毁前都是存在的。

demo1:读取上级作用域的活动变量

function foo() {
  let x = 10;
  // 闭包,捕获`foo`的环境。
  // 当foo被调用时,创建foo的执行环境,初始化变量对象(变量声明和方法声明以及参数)
  //当捕捉到bar这个函数声明时,会在函数内部创建bar的[[scope]]=foo的活动变量+foo的[[scope]]
  function bar() {
    return x;
  }

  return bar;
}
let x = 20;
// 调用`foo`来返回`bar`闭包。
//当执行到这个地方的时候,相当于定义了一个函数bar,bar的[[scope]]=foo的活动变量+foo的[[scope]]
// 只要全局作用域不销毁,那么bar的[[scpoe]]就不会销毁,因此形成闭包(调用的函数foo()执行完毕后其执行环境应该销毁的,但是由于此处的函数表达式而没有销毁foo的执行环境即bar的[[scope]],从而形成闭包)
let bar = foo();
bar(); // 10,而不是20!

demo2:读写上级作用域的活动变量

function createCounter() {
  let count = 0;
  return {
    increment() { count++; return count; },
    decrement() { count--; return count; },
  };
}
let counter = createCounter();
console.log(
  counter.increment(), // 1
  counter.decrement(), // 0
  counter.increment(), // 1
);

你可能感兴趣的:(闭包,看这篇就够了!)