理解JavaScript的执行上下文和执行栈
参考借鉴 Understanding Execution Context and Execution Stack in Javascript
执行上下文是什么
执行上下文,简单来说就是JavaScript代码在被解析和执行时所处环境的抽象概念,JavaScript代码必定运行在某个执行上下文中。
执行上下文的类型
Javascript中有三种执行上下文
- 全局执行上下文
- 函数执行上下文
- Eval函数执行上下文
执行上下文的生命周期
执行上下文的生命周期包括三个阶段:创建阶段→执行阶段→回收阶段。
创建阶段
创建阶段会做三件事情:
- 绑定执行上下文中的
this。 - 创建变量环境。
- 创建作用域链。
绑定执行上下文的 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()