😉 你好呀,我是爱编程的Sherry,很高兴在这里遇见你!我是一名拥有十多年开发经验的前端工程师。这一路走来,面对困难时也曾感到迷茫,凭借不懈的努力和坚持,重新找到了前进的方向。我的人生格言是——认准方向,坚持不懈,你终将迎来辉煌!欢迎关注我😁,我将在这里分享工作中积累的经验和心得,希望我的分享能够给你带来帮助。
引言
在 JavaScript 中,执行上下文(Execution Context)和执行栈(Execution Stack)是理解 JavaScript 代码执行过程中的两个非常关键的概念。它们是 JavaScript 引擎在运行代码时,管理执行流程和变量作用域的核心机制。下面将详细解释这两个概念。
执行上下文(Execution Context)
定义:
执行上下文是 JavaScript 代码执行时的环境,决定了代码如何被解析和执行。每当 JavaScript 函数被调用、脚本被加载时,都会创建一个新的执行上下文。
执行上下文的类型:
JavaScript 中主要有三种类型的执行上下文:
全局执行上下文:
默认创建的执行上下文,代码最外层的上下文。在浏览器中,this
指向 window
对象。任何未定义在函数内的变量或函数,都会被添加到全局上下文中。
函数执行上下文:
每次调用一个函数时,都会为该函数创建一个新的执行上下文。每个函数有自己独立的上下文,并且在执行过程中会有自己的变量、参数和内部函数。
eval 执行上下文(较少用):
当使用 eval()
函数时,JavaScript 会将字符串内容当作代码执行,这时会创建一个独立的执行上下文。
执行上下文的生命周期:
每个执行上下文的生命周期大致可以分为两个阶段:
创建阶段:
变量对象(Variable Object)/ 词法环境(Lexical Environment):
变量、函数声明和参数被存储在执行上下文的变量对象中。
作用域链(Scope Chain):
作用域链确定了当前执行代码在哪些环境中可以访问变量。
this 绑定:
this 会根据上下文不同绑定到不同的对象(如全局对象或调用该函数的对象)。
执行阶段:
变量赋值、函数执行,代码依次执行。
举个栗子:
var name = 'Alice';
function sayHello() {
var greeting = 'Hello';
console.log(greeting + ', ' + name);
}
sayHello();
全局执行上下文:
创建变量 name
,并赋值为 'Alice'
。sayHello
函数被声明但尚未执行。
函数执行上下文(当调用 sayHello() 时):
创建 greeting
变量,并赋值为 'Hello'
。执行 console.log
,通过作用域链可以访问全局变量 name
。
执行栈(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. [ ]
执行上下文与执行栈的关系
执行上下文是 JavaScript 代码的执行环境。每当 JavaScript 引擎执行代码时,会为当前代码块(如全局代码、函数调用)创建一个新的执行上下文。
执行栈是管理这些执行上下文的结构。函数调用会在执行栈中按顺序推入新的执行上下文,执行完毕后从栈中弹出。
执行上下文和执行栈的使用场景
递归函数:
递归调用函数时,每次递归都会推入一个新的执行上下文,形成多个嵌套的执行栈帧。当递归结束时,执行上下文逐个弹出。
示例:
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
的回调在全局上下文结束后才会被执行。
总结
执行上下文:是 JavaScript 代码的运行环境,决定了代码执行时的变量、作用域和 this
绑定。主要有三种类型:全局执行上下文、函数执行上下文和 eval
执行上下文。
执行栈:是管理执行上下文的栈结构,遵循后进先出的原则,用来记录函数调用和返回的顺序。
执行栈和执行上下文是 JavaScript 代码运行的核心机制,掌握它们有助于理解 JavaScript 的函数调用、作用域链和异步执行等高级特性。