闭包
首先借用阮老师对闭包(closure)的概念做出的定义:
在《JavaScript高级程序设计(第3版)》中文版中[3],具体描述在第7章函数表达式第7.2节(页码为第178页):闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
ECMAScript中,闭包指的是:
从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包:
- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
- 在代码中引用了自由变量
闭包是怎么保存数据作为缓存数据使用?
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是闭包的情况又有所不同,闭包是一个函数捕获它被定义时所在的环境,这个环境在该函数的引用被销毁前都是存在的。
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
);