跳到主要内容

执行栈与执行上下文

本文讨论 ES5 中的执行上下文与执行栈的概念

省流:

执行上下文(EC)形成于 JavaScript 解释器执行代码的过程中,负责准备代码执行的环境

  • 分为三类,全局、函数和 eval

  • 包括变量(VO)、作用域链、和 this

  • 生命周期有两个阶段:创建阶段和执行阶段

  • 创建

    • 创建 VO

      1. 找形参赋值

      2. 找 arguments 赋值

      3. 函数声明赋值

      4. var 赋值 undefined

    • 确定 this

    • 确定作用域

  • 执行

    1. 上阶段的变量赋值(已经有值的就覆盖了)
    2. 逐行执行

执行栈也称调用栈,是 JavaScript 解析器管理执行上下文的结构或机制

执行上下文

Execution Context,负责代码(全局、函数)执行之前的准备工作,即代码执行的环境,也称执行上下文环境

  • 全局执行上下文

JavaScript 在运行时,首先会进入全局环境,生成全局执行上下文。首先创建一个全局的 window 对象,并且设置 this 的值等于这个全局对象。一个程序中有且仅有一个全局上下文。

  • 函数执行上下文

调用函数时,就会创建一个函数上下文

  • Eval 函数执行上下文

执行在 eval 函数内部的代码也会有它属于自己的执行上下文

执行栈(调用栈)

执行栈也称调用栈,就是 JavaScript 解析器管理执行上下文的结构或机制

当程序进入一个执行环境时,它的执行上下文对象就会被创建并入栈;程序执行完成时,它的执行上下文就会被摧毁并出栈

处于栈底的一定是全局执行上下文,而处于栈顶的是当前正在执行函数的上下文

如果执行上下文数量超过栈分配的空间,会造成栈溢出,常见于递归调用,没有终止条件造成死循环的场景

执行上下文的生命周期

执行上下文具体会做哪些准备工作呢?

执行上下文的生命周期共包含两个阶段:

创建阶段

创建变量对象的行为也是面试中的主要考点

  1. 创建变量对象(VO: variable object
    1. 确定函数的形参(并赋值)
    2. 函数环境会初始化 arguments 对象(并赋值)
    3. 确定普通字面量形式的函数声明(并赋值)
    4. var 变量声明,var 函数表达式声明(未赋值
    5. 对于已经声明的变量,不会重复声明
  2. 确定 this 指向
  3. 确定作用域
executionContextObj = {
variableObject: {}, // 变量对象,包括 arguments 对象、形参、函数和局部变量
scopeChain: {},
this: {}
}

示例:

const foo = function(i) {
console.log(b)
console.log(c)
var a = "Hello"
var b = function privateB() {}
function c(){}
}
foo(10)

// 生成的 VO 为
VO = {
i: 10, // 1
arguments: { 0: 10, length: 1 }, // 2
c: pointer to funcC, // 3
a: undefined, // 4
b: undefined, // 4
}

// 因此输出结果为 undefined 与 [Function c]

执行阶段

  1. 变量对象赋值
    1. 变量赋值
    2. 函数表达式赋值
  2. 调用函数
  3. 顺序执行其它代码

以下是代码示例

(function (){
console.log(typeof foo)
console.log(typeof bar)
var foo = "Hello"
var bar = function (){
return "World"
}
function foo() {
return "good"
}
console.log(foo, typeof foo)
})()

// 函数立即执行,创建阶段的 VO
VO = {
arguments: { length: 0 },
foo: pointer to function foo(){}, // 普通字面量形式的函数声明
// foo 已经声明过了,所以 var foo 在创建阶段不生效
bar: undefined
}

// 执行阶段,逐行执行
// 打印 function
// 打印 undefined
VO = {
arguments: { length: 0 },
foo: 'Hello'
bar: pointer to function (){},
}
// 最后打印 'Hello', string

IIFE

立即执行函数表达式(Immediately Invoked Function Expression,IIFE)是在定义后立即执行的JavaScript函数。IIFE的主要用途包括:

  1. 创建新的作用域:在ES5之前,JavaScript没有块级作用域,只有全局作用域和函数作用域。IIFE允许开发者为变量和函数创建一个新的局部作用域,避免污染全局命名空间。
  2. 封装私有变量(结合闭包):通过IIFE,可以封装私有变量和函数,提供一个隔离的空间,在这个空间内,变量和函数不会暴露到全局作用域,从而防止外部的访问和冲突。
  3. 模块模式:在模块系统出现之前,IIFE常用于模拟模块模式(Module Pattern),通过这种方式可以模拟出公共API的暴露和私有变量的封装。
  4. 避免临时变量泄露:在循环或者复杂的计算中,可以使用IIFE来避免中间变量泄露到外部作用域。
  5. 初始化工作:当你需要在页面加载时执行一些初始化代码,但又不想创建额外的命名函数时,可以使用IIFE来执行这些引导代码。