JavaScript 基础知识整理之闭包

220 阅读3分钟

什么是闭包

MDN对闭包的定义:

那些能够访问自由变量的函数(能够读取别的函数作用域的变量的函数)

自由变量

自由变量是指在函数中使用,既不是函数传进来的参数也不是函数局部变量的变量。

举个栗子:

let a = 1
function fn() {
  console.log(a)
}
fn()

上述代码中 函数fn中可以访问a变量,a既不是函数fn传进来的变量又不是fn自身局部作用局中的变量。所以a是自由变量。

那么函数fn 和 变量a就构成了一个闭包。。。正如《JavaScript权威指南》中所讲:从技术层面讲,JavaScript的所有函数都是闭包

有些小伙伴可能会有疑问,这和平时遇到的闭包好像不太一样,那么我们再看下面代码:

var scope = "out";
function out(){
    var scope = "inner";
    function inner(){
        console.log(scope);
    }
    return inner;
}

var foo = out();
foo(); //inner

我们先分析这段代码的执行过程:

  • 进入全局代码,创建** 全局执行上下文 ,将全局执行上下文压如执行上下文栈**
  • 全局执行上下文初始化
  • 执行out函数,创建out函数执行上下文,将out函数执行上下文压如执行上下文栈
  • out函数执行上下文初始化,创建建变量对象、作用域、this等
  • out函数执行完毕,out函数执行上下文执行上下文栈中弹出
  • 执行inner函数,创建inner函数执行上下文,将inner函数执行上下文压入执行上下栈
  • inner函数执行上下文初始化,创建变量对象、作用域、this等
  • inner函数执行完毕,inner函数执行上下文执行上下文栈中弹出

由这段代码的执行过程,你回意外发现,在执行inner函数的时候,out函数执行上下文已经被销毁(从执行上下文栈)弹出,为什么还可以读取到out函数作用域下的scope值呢?

inner函数执行上下文维护了一个作用域链

innerContext = {
    Scope: [AO, outContext.AO, globalContext.VO],
}

由于函数作用域链的存在,即使out函数执行上下文被弹出,,但是JavaScript依然会让outContext.AO存在内存中,inner函数依然可以通过inner函数的作用域链找到他。

从栈的角度看待JavaScript函数

栈是一种先进后出的数据结构:

  • 执行out函数前,我们处于全局执行环境。
  • 进入 out,往栈中push一个out执行环境,次有有变量scope,和函数对象inner。此时不仅可以访问自身执行环境所定义的变量,还可以访问全局执行环境所定义变量
  • 进入inner 往栈中push一个inner执行环境。此时可以访问out执行环境变量和全局执行环境变量。因为程序在访问变量时,是向底层栈一个个找,如果找到全局执行环境里都没有对应变量,则程序抛出underfined的错误。
  • 随着inner函数的执行完毕,inner执行环境销毁。接着执行完out,out执行环境销毁。

所以,当执行foo = out() 时,其实outer的执行环境并没有被销毁,因为他里面的变量scope仍然被被inner的函数作用域链所引用,当程序执行完foo(), 这时候,inner和outer的执行环境才会被销毁调

综上,我们可以从实践角度上重新定义一下闭包:

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