跳至主要內容

JavaScript 中执行上下文和执行栈是什么?


JavaScript 中执行上下文和执行栈是什么?

一、执行上下文(Execution Context)

执行上下文是 JavaScript 代码执行的运行环境,包含了变量、函数及其作用域链等关键信息。任何 JavaScript 代码都必须运行在某个执行上下文中。

执行上下文的类型有以下三种:

  1. 全局执行上下文

    • 全局代码的默认环境。
    • 创建时会绑定一个全局对象(浏览器中为 window,Node.js 中为 global),并将 this 指向全局对象。
  2. 函数执行上下文

    • 每当一个函数被调用时,都会为该函数创建一个新的执行上下文。
    • 包含函数内部的变量、参数和 this 的绑定。
  3. Eval 执行上下文

    • 仅在 eval() 函数中运行的代码会创建此上下文。
    • 较少使用且不推荐。

二、执行上下文的生命周期

每个执行上下文都有完整的生命周期,由以下三个阶段组成:

  1. 创建阶段

    • 确定 this 的值(This Binding)
      根据调用方式确定 this 的值(全局上下文中指向全局对象,函数上下文中取决于调用形式)。
    • 创建词法环境(Lexical Environment)
      • 包含环境记录(存储变量和函数声明)和外部环境的引用(作用域链)。
      • 全局环境与函数环境的结构不同,全局环境的外部引用为 null
    • 创建变量环境(Variable Environment)
      • 类似于词法环境,但专门用于处理 var 声明的变量。
      • 在 ES6 中,letconst 被存储在词法环境中,而 var 被存储在变量环境中。
  2. 执行阶段

    • 变量和函数声明被赋值。
    • 执行具体代码。
  3. 销毁阶段

    • 当前执行上下文被销毁,内存释放。
    • 若该上下文有闭包引用,则可能不会立即销毁。

三、执行栈(Execution Stack)

执行栈(又称调用栈)是一种后进先出(LIFO)的数据结构,用于管理代码执行过程中创建的执行上下文。

工作流程

  1. 全局执行上下文入栈
    脚本开始执行时,全局执行上下文被压入栈中。

  2. 函数执行上下文入栈
    每当函数被调用时,都会为该函数创建新的执行上下文并压入栈顶。

  3. 执行栈顶上下文
    JavaScript 引擎总是运行栈顶的执行上下文。

  4. 函数执行完毕
    栈顶的执行上下文被弹出,控制权回到下一个上下文。

  5. 全局执行上下文出栈
    所有代码执行完毕后,全局执行上下文出栈,执行栈清空。

示例代码

function foo() {
	console.log('foo start');
	bar();
	console.log('foo end');
}

function bar() {
	console.log('inside bar');
}

foo();
console.log('Global end');

栈变化

执行步骤执行栈内容
初始化[Global]
调用 foo()[Global, foo]
调用 bar()[Global, foo, bar]
bar() 执行完毕[Global, foo]
foo() 执行完毕[Global]
全局上下文结束[]

通过图表和过程,可以直观了解 JavaScript 如何管理代码执行和上下文。

四、注意事项

  1. 变量提升

    • 在创建阶段,var 声明的变量会被初始化为 undefined
    • letconst 声明的变量处于暂时性死区,必须等执行到声明语句才能访问。
  2. 闭包
    闭包引用了外部上下文中的变量,可能会延长执行上下文的生命周期,导致变量无法被销毁。

  3. 递归调用
    每次递归调用都会生成新的执行上下文。如果递归层数过多,可能导致栈溢出。