[路飞]理解JavaScript的执行上下文和执行栈

162 阅读3分钟

理解JavaScript的执行上下文和执行栈

参考借鉴 Understanding Execution Context and Execution Stack in Javascript

执行上下文是什么

执行上下文,简单来说就是JavaScript代码在被解析和执行时所处环境的抽象概念,JavaScript代码必定运行在某个执行上下文中。

执行上下文的类型

Javascript中有三种执行上下文

  • 全局执行上下文
  • 函数执行上下文
  • Eval函数执行上下文

执行上下文的生命周期

执行上下文的生命周期包括三个阶段:创建阶段→执行阶段→回收阶段。

创建阶段

创建阶段会做三件事情:

  1. 绑定执行上下文中的 this
  2. 创建变量环境。
  3. 创建作用域链。
绑定执行上下文的 this

首先我们要明白一个问题,this 只有在代码被执行的时候才会被确认,在函数定义的时候没办法确认。 因为只有在创建执行上下文的时候,this 才会被绑定。

在全局执行上下文中,this 指向全局对象,在浏览器中,既是指向 window 对象。

在函数执行上下文中,this 取决于函数如何被调用。如果是被某个对象调用,则指向调用的对象,否则 this 会被设置成全局对象或者 undefined(在严格模式下)。

创建环境变量

首先初始化函数的参数arguments,提升函数声明和变量声明。

创建作用域链

在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。

执行阶段

在此阶段所以的准备工作做完,就可以执行代码了。

回收阶段

执行栈弹出执行上下文,等待回收。

执行栈

执行栈可以理解为执行上下文的管理工具,其本质是一个后进先出的调用栈。用于管理一段脚本中的执行上下文,并控制一段脚本中代码片段的执行顺序。

当一段JS脚本被执行时,会首先创建一个全局执行上下文,本放入执行栈中,在代码执行的过程中,如果有函数的调用,将会创建一个函数执行上下文并且放入执行栈中,当函数执行完毕,执行栈弹出该执行上下文。

有如下一段脚本代码:

function fn1() {
    console.log('fn1')
    fn2()
}

function fn2() {
    console.log('fn2')
}

fn1()

我们用一段伪代码来模拟执行以上js脚本时,执行栈所作的工作。

定义一个后进先出的执行栈

const ECStack = []

当该段脚本被执行的时候,会首先创建一个全局执行上下文,并压入ECStack执行栈中

const GlobalContext // 创建全局执行上下文
ECStack.push(GlobalContext) // 入栈

然后继续执行脚本,当解析到 fn1() 代码片段时,出现了函数调用

const Fn1FunctionContext // 创建fn1的函数执行上下文
ECStack.push(Fn1FunctionContext) // 入栈

此时,执行栈的栈顶是 fn1 的执行上下文,于是执行 fn1 执行上下文中的代码片段,也就是执行 fn1 函数 ,在 fn1 中出现了 fn2 函数的调用,此时将创建 fn2 的执行上下文

const Fn2FunctionContext // 创建fn2的函数执行上下文
ECStack.push(Fn2FunctionContext) //入栈

此时,执行栈的栈顶是 fn2 的执行上下文,于是执行 fn2 , fn2 中没有出现函数的调用,执行完毕后执行栈弹出该执行上下文。

ECStack.pop()

然后继续执行 Fn1FunctionContext ,执行完毕后弹出该执行上下文

ECStack.pop()