菜单

执行上下文

相关源文件

目的

本文档解释了 JavaScript 的执行上下文——JavaScript 代码被评估和执行的环境。它涵盖了 JavaScript 引擎如何创建和管理执行上下文、调用栈机制以及执行上下文与作用域之间的关系。有关异步 JavaScript 执行的信息,请参阅 异步 JavaScript

什么是执行上下文?

执行上下文是 JavaScript 中的一个基本概念,它定义了 JavaScript 代码运行的环境。它包含有关变量、函数声明、作用域链以及 this 关键字的值的信息。每次执行 JavaScript 代码时,它都会在一个执行上下文中进行。

根据 ECMAScript 规范,每个执行上下文都有三个主要组成部分:

  1. 变量环境——存储变量声明、函数声明和函数参数
  2. 词法环境——类似于变量环境,但用于具有块级作用域的 letconst 声明
  3. ThisBinding——对当前上下文中的 this 值的引用

来源: README.md117-118

执行上下文的类型

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

全局执行上下文

全局执行上下文是在 JavaScript 脚本首次加载时创建的。它代表了代码执行的基准环境。在此上下文中:

  • 创建了全局对象(浏览器中为 window,Node.js 中为 global)。
  • this 值设置为引用全局对象。
  • 在全局作用域中声明的变量和函数成为全局对象的属性。

函数执行上下文

每次调用函数时都会创建一个新的函数执行上下文。此上下文包括:

  • 传递给函数的参数
  • 在函数内声明的局部变量
  • 对函数外部环境的引用(用于访问作用域链)
  • this 的值(取决于函数如何被调用)

Eval 执行上下文

eval() 函数内执行代码时创建。由于安全和性能方面的考虑,这在现代 JavaScript 应用程序中很少使用。

来源: README.md117-118 README.md297-312

执行上下文的创建与执行

JavaScript 的执行发生在两个阶段:

创建阶段

在代码执行之前,JavaScript 引擎会:

  1. 创建变量环境——为变量和函数分配内存
  2. 设置作用域链——链接到外部环境
  3. 确定 this 的值

在此阶段,变量声明被初始化为 undefined,函数声明被存储在内存中(提升)。

执行阶段

在此阶段,JavaScript 引擎逐行执行代码:

  1. 为变量赋值
  2. 执行函数表达式
  3. 调用函数时创建新的执行上下文

来源: README.md117-118 README.md468-474

调用栈与执行上下文

调用栈是一个数据结构,用于跟踪代码执行期间的执行上下文。

调用栈的工作原理

  1. 当 JavaScript 开始运行时,会创建一个全局执行上下文并将其推入调用栈。
  2. 当调用函数时,会创建一个新的执行上下文并将其推入栈顶。
  3. 当当前函数完成时,其执行上下文会从栈中弹出。
  4. 引擎会从栈中的下一个上下文恢复执行。
  5. 这个过程遵循后进先出 (LIFO) 的原则。

调用栈示例

考虑以下代码片段:

调用栈将按以下方式处理:

来源: README.md102-108 README.md126-136

作用域与执行上下文

作用域决定了变量和函数在代码不同部分的可访问性。它与执行上下文密切相关,但它们并非完全相同。

作用域和执行上下文之间的关系

  • 执行上下文:代码执行的环境,包括变量环境、词法环境和 this 绑定。
  • 作用域:代码不同部分中变量可访问性的规则。

执行上下文在代码执行时创建,而作用域在编写代码时(词法作用域)确定。

作用域链

作用域链允许内部函数访问外部函数和全局作用域中的变量。

  1. 每个执行上下文都有对其外部环境的引用。
  2. 在查找变量时,JavaScript 首先检查局部作用域。
  3. 如果未找到,它会继续沿着作用域链向上查找外部作用域。
  4. 这个过程一直持续到找到全局作用域。

来源: README.md284-312 README.md315-323

JavaScript 引擎与执行上下文

JavaScript 引擎实现了 ECMAScript 规范,在代码执行期间创建和管理执行上下文。

引擎如何处理 JavaScript 代码

  1. 解析阶段:引擎将代码解析成抽象语法树 (AST)。
  2. 编译阶段:引擎可能会使用即时 (JIT) 编译来编译代码。
  3. 执行阶段:引擎创建执行上下文并执行代码。
  • V8 (Chrome, Node.js):使用 JIT 编译将 JavaScript 转换为机器码。
  • SpiderMonkey (Firefox):Mozilla 的 JavaScript 引擎。
  • JavaScriptCore (Safari):Apple 的 JavaScript 引擎。
  • Chakra (旧版 Edge):Microsoft 的 JavaScript 引擎。

来源: README.md450-474

执行上下文优化

现代 JavaScript 引擎采用各种优化来提高执行上下文的性能:

  1. 隐藏类:通过创建内部类型系统来优化属性访问。
  2. 内联缓存:加速重复的属性访问操作。
  3. 执行上下文重用:为以相似模式调用的函数重用上下文。
  4. 函数内联:对于频繁调用的函数,用函数体替换函数调用。

来源: README.md461-463

实际影响

理解执行上下文对 JavaScript 开发人员有几个实际意义:

  1. 提升 (Hoisting):变量和函数声明在代码执行前被处理。
  2. 闭包 (Closures):函数在执行结束后仍然可以访问其词法环境。
  3. this 绑定this 的值取决于函数的调用方式。
  4. 作用域链:函数可以访问父作用域中的变量。
  5. 性能考虑:深度嵌套的作用域会影响查找性能。

来源: README.md117-121 README.md834-848

常见陷阱和最佳实践

常见陷阱

  • varletconst 之间令人困惑的变量提升行为。
  • 事件处理程序和回调中意外的 this 绑定。
  • 通过不当的闭包使用导致内存泄漏。
  • 由于无限递归导致的调用栈溢出。

最佳实践

  • 使用 letconst 代替 var 以获得更可预测的作用域。
  • 使用箭头函数来维护词法 this 绑定。
  • 编写递归函数时要注意调用栈。
  • 了解闭包如何影响变量生命周期和内存使用。

来源: README.md117-121 README.md297-312 README.md580-589