JavaScript 中执行上下文和执行栈是什么?
JavaScript 中执行上下文和执行栈是什么?
一、执行上下文(Execution Context)
执行上下文是 JavaScript 代码执行的运行环境,包含了变量、函数及其作用域链等关键信息。任何 JavaScript 代码都必须运行在某个执行上下文中。
执行上下文的类型有以下三种:
全局执行上下文
- 全局代码的默认环境。
- 创建时会绑定一个全局对象(浏览器中为
window
,Node.js 中为global
),并将this
指向全局对象。
函数执行上下文
- 每当一个函数被调用时,都会为该函数创建一个新的执行上下文。
- 包含函数内部的变量、参数和
this
的绑定。
Eval 执行上下文
- 仅在
eval()
函数中运行的代码会创建此上下文。 - 较少使用且不推荐。
- 仅在
二、执行上下文的生命周期
每个执行上下文都有完整的生命周期,由以下三个阶段组成:
创建阶段
- 确定
this
的值(This Binding)
根据调用方式确定this
的值(全局上下文中指向全局对象,函数上下文中取决于调用形式)。 - 创建词法环境(Lexical Environment)
- 包含环境记录(存储变量和函数声明)和外部环境的引用(作用域链)。
- 全局环境与函数环境的结构不同,全局环境的外部引用为
null
。
- 创建变量环境(Variable Environment)
- 类似于词法环境,但专门用于处理
var
声明的变量。 - 在 ES6 中,
let
和const
被存储在词法环境中,而var
被存储在变量环境中。
- 类似于词法环境,但专门用于处理
- 确定
执行阶段
- 变量和函数声明被赋值。
- 执行具体代码。
销毁阶段
- 当前执行上下文被销毁,内存释放。
- 若该上下文有闭包引用,则可能不会立即销毁。
三、执行栈(Execution Stack)
执行栈(又称调用栈)是一种后进先出(LIFO)的数据结构,用于管理代码执行过程中创建的执行上下文。
工作流程
全局执行上下文入栈
脚本开始执行时,全局执行上下文被压入栈中。函数执行上下文入栈
每当函数被调用时,都会为该函数创建新的执行上下文并压入栈顶。执行栈顶上下文
JavaScript 引擎总是运行栈顶的执行上下文。函数执行完毕
栈顶的执行上下文被弹出,控制权回到下一个上下文。全局执行上下文出栈
所有代码执行完毕后,全局执行上下文出栈,执行栈清空。
示例代码
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 如何管理代码执行和上下文。
四、注意事项
变量提升
- 在创建阶段,
var
声明的变量会被初始化为undefined
。 let
和const
声明的变量处于暂时性死区,必须等执行到声明语句才能访问。
- 在创建阶段,
闭包
闭包引用了外部上下文中的变量,可能会延长执行上下文的生命周期,导致变量无法被销毁。递归调用
每次递归调用都会生成新的执行上下文。如果递归层数过多,可能导致栈溢出。