## 一、什么是闭包
<p align=left> 当通过调用外部函数返回的内部函数后,即使外部函数已经执行结束了,但是被内部函数引用的外部函数的变量依然会保存在内存中,我们把引用了其他函数作用域变量的函数和这些被引用变量的集合,称为闭包(Closure),闭包是这些东西共同的组合。</p>
<p align=left> 简单来说,闭包就是指一个函数可以访问另一个函数作用域内的变量。在JavaScript中,每个函数都是一个闭包,因为它们都可以访问自己的作用域内的变量,以及外层函数作用域内的变量</p>
## 二、如何实现闭包
闭包是指一个函数可以访问它定义时所在的词法作用域以及全局作用域中的变量。在JavaScript中,闭包可以通过函数嵌套和变量引用实现。
```js
function outerFunction() {
let outerVariable = '我在outer函数里!';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const innerFunc = outerFunction();
innerFunc(); // 输出: 我在outer函数里!
```
在上面的代码示例中,`innerFunction`引用了`outerVariable`,因此JavaScript引擎会保留`outerFunction`的作用域链,以便`innerFunction`可以访问`outerVariable`
```js
function a(){
function b(){
var bb = 888
console.log(aa); //输出:666
}
var aa = 666
return b
}
var demo = a()
demo()
```
在上面的代码示例中,`a`函数定义了一个名为`aa`的变量和一个名为`b`的函数,`b`函数引用了`aa`变量,因此JavaScript引擎会保留`a`函数的作用域链,`b`函数可以访问`a`函数的执行上下文,`b`函数内用到了外部函数`a`的变量`aa`,在`a`函数调用结束后该函数执行上下文会销毁,但会保留一部分留在内存中供`b`函数使用,这就形成了闭包。也就是说:当内部函数引用外部函数的变量时,外部函数的作用域链将被保留在内存中,以便内部函数可以访问这些变量。这种函数嵌套和变量共享的方式就是闭包的核心概念。当一个函数返回另一个函数时,它实际上返回了一个闭包,其中包含了原函数定义时的词法作用域和相关变量。
## 三、什么是作用域链
全局代码存储其变量的地方叫做变量对象(VO),函数存储其变量的叫活动对象(AO),VO 和 AO 都是在预编译时确定其内容,然后在代码运行时被修改值。
每一个函数都有一个 `[[Scopes]]` 属性,其存储的是这个函数运行时的作用域链,除了当前函数的 AO,作用域链的其他部分都会在其父函数预编译时添加到函数的 `[[Scopes]]` 属性上(因为父函数也需要预编译后才能确定自己的AO),所以 js 的作用域是词法作用域。
```js
// 1: global.VO = {t}
let t = 111
function fun(){
// 3: fun.AO = {a,b}
let a = 1
let b = 2
function fun1() {
// 5: fun1.AO = {c}
let c = 3
}
// 4: fun1.[[Scopes]] = [...fun.[[Scopes]], fun.AO]
}
// 2: fun.[[Scopes]] = [global.VO]
fun()
```
<p align=left>上面代码在 `fun()` 被调用前,会立即预编译 `fun` 函数,这一步会得到 `fun` 的活动对象(AO),然后运行 fun 函数,在执行到 `let a = 1` 的时候,会将变量对应到 a 属性改成 1。后面也是一样。`[[Scopes]]` 就像一个数组一样,每一个函数的 `[[Scopes]]` 中都存在当前函数的 AO 和上级函数的 `[[Scopes]]`。在函数运行时会优先取距离当前函数 AO 近的变量值,这就是作用域的就近原则。</p>
<p align=left>但是最新的 V8 中已经发生了变化(Chrome 中已经可以看到这些变化),在为一个函数绑定词法作用域时,并不会粗暴的直接把父函数的 AO 放入其 `[[Scopes]]` 中,而是会分析这个函数中会使用父函数 AO 中的哪些变量,而这些可能会被使用到的变量会被存储在一个叫做 `Closure` 的对象中,每一个函数都有且只有一个 `Closure` 对象,最终这个 `Closure `将会代替父函数的 AO 出现在子函数的 `[[Scopes]]` 中</p>
### 闭包对象
在V8中每一个函数执行前都会进行预编译,预编译阶段都会执行3个重要的字节码
1. CreateFunctionConte