在 JavaScript 中,执行上下文(Execution Context)和执行栈(Execution Stack)是理解 JavaScript 代码执行过程中的两个非常关键的概念。它们是 JavaScript 引擎在运行代码时,管理执行流程和变量作用域的核心机制。下面将详细解释这两个概念。
1. 执行上下文(Execution Context)
定义:
执行上下文是 JavaScript 代码执行时的环境,决定了代码如何被解析和执行。每当 JavaScript 函数被调用、脚本被加载时,都会创建一个新的执行上下文。
执行上下文的类型:
JavaScript 中主要有三种类型的执行上下文:
-
全局执行上下文:
- 默认创建的执行上下文,代码最外层的上下文。
- 在浏览器中,
this
指向window
对象。 - 任何未定义在函数内的变量或函数,都会被添加到全局上下文中。
-
函数执行上下文:
- 每次调用一个函数时,都会为该函数创建一个新的执行上下文。
- 每个函数有自己独立的上下文,并且在执行过程中会有自己的变量、参数和内部函数。
-
eval
执行上下文(较少用):- 当使用
eval()
函数时,JavaScript 会将字符串内容当作代码执行,这时会创建一个独立的执行上下文。
- 当使用
执行上下文的生命周期:
每个执行上下文的生命周期大致可以分为两个阶段:
-
创建阶段:
- 变量对象(Variable Object)/ 词法环境(Lexical Environment):
- 变量、函数声明和参数被存储在执行上下文的变量对象中。
- 作用域链(Scope Chain):
- 作用域链确定了当前执行代码在哪些环境中可以访问变量。
this
绑定:this
会根据上下文不同绑定到不同的对象(如全局对象或调用该函数的对象)。
- 变量对象(Variable Object)/ 词法环境(Lexical Environment):
-
执行阶段:
- 变量赋值、函数执行,代码依次执行。
示例:
var name = 'Alice';
function sayHello() {
var greeting = 'Hello';
console.log(greeting + ', ' + name);
}
sayHello();
-
全局执行上下文:
- 创建变量
name
,并赋值为'Alice'
。 sayHello
函数被声明但尚未执行。
- 创建变量
-
函数执行上下文(当调用
sayHello()
时):- 创建
greeting
变量,并赋值为'Hello'
。 - 执行
console.log
,通过作用域链可以访问全局变量name
。
- 创建
2. 执行栈(Execution Stack)
定义:
执行栈(也称作调用栈,Call Stack)是用来管理多个执行上下文的栈结构。当一个函数被调用时,其对应的执行上下文会被推入执行栈;当函数执行完毕后,执行上下文会从栈中弹出。
执行栈的特点:
- 后进先出(LIFO):执行栈遵循后进先出的原则,最后进入的执行上下文最先被执行完毕。
- 全局执行上下文位于栈底:全局执行上下文是整个程序最开始执行的环境,它始终处于栈底。
执行过程:
- 当 JavaScript 引擎开始执行代码时,会首先创建全局执行上下文,并将其推入执行栈。
- 当函数被调用时,会为该函数创建一个新的执行上下文,并推入栈中。
- 当函数执行完毕时,执行上下文会从栈中弹出,控制权返回到上一个执行上下文。
示例:
function multiply(x, y) {
return x * y;
}
function square(n) {
return multiply(n, n);
}
function printSquare(num) {
var result = square(num);
console.log(result);
}
printSquare(5);
执行栈过程:
- 全局执行上下文被创建并推入执行栈。
- 执行到
printSquare(5)
,为printSquare
创建执行上下文并推入栈中。 printSquare
中调用square(5)
,创建square
的执行上下文并推入栈中。square(5)
中调用multiply(5, 5)
,创建multiply
的执行上下文并推入栈中。multiply
函数执行完毕,执行上下文从栈中弹出,返回控制权给square
。square
执行完毕,执行上下文弹出,返回控制权给printSquare
。printSquare
执行完毕,执行上下文弹出,返回控制权给全局执行上下文。- 最终,程序执行完毕,全局执行上下文也会弹出。
可视化:
执行栈变化:
1. [ 全局执行上下文 ]
2. [ 全局执行上下文, printSquare 执行上下文 ]
3. [ 全局执行上下文, printSquare 执行上下文, square 执行上下文 ]
4. [ 全局执行上下文, printSquare 执行上下文, square 执行上下文, multiply 执行上下文 ]
5. [ 全局执行上下文, printSquare 执行上下文, square 执行上下文 ]
6. [ 全局执行上下文, printSquare 执行上下文 ]
7. [ 全局执行上下文 ]
8. [ ]
3. 执行上下文与执行栈的关系
- 执行上下文是 JavaScript 代码的执行环境。每当 JavaScript 引擎执行代码时,会为当前代码块(如全局代码、函数调用)创建一个新的执行上下文。
- 执行栈是管理这些执行上下文的结构。函数调用会在执行栈中按顺序推入新的执行上下文,执行完毕后从栈中弹出。
4. 执行上下文和执行栈的使用场景
-
递归函数:递归调用函数时,每次递归都会推入一个新的执行上下文,形成多个嵌套的执行栈帧。当递归结束时,执行上下文逐个弹出。
示例:
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5);
每次递归调用
factorial
都会创建一个新的执行上下文并推入执行栈。 -
异步代码执行:异步代码(如
setTimeout
)并不会立即创建新的执行上下文,而是被放入任务队列中,当栈清空后再执行。示例:
console.log('Start'); setTimeout(() => { console.log('Async task'); }, 0); console.log('End');
输出顺序是:
Start End Async task
因为
setTimeout
的回调在全局上下文结束后才会被执行。
5. 总结
-
执行上下文:是 JavaScript 代码的运行环境,决定了代码执行时的变量、作用域和
this
绑定。主要有三种类型:全局执行上下文、函数执行上下文和eval
执行上下文。 -
执行栈:是管理执行上下文的栈结构,遵循后进先出的原则,用来记录函数调用和返回的顺序。
-
执行栈和执行上下文是 JavaScript 代码运行的核心机制,掌握它们有助于理解 JavaScript 的函数调用、作用域链和异步执行等高级特性。