1. 执行上下文(Execution Context)
执行上下文是代码运行时的环境,所有的 JavaScript 代码都是在执行上下文中执行的。它可以分为以下几种类型:
- 全局执行上下文:JavaScript 程序开始运行时创建的执行上下文,只有一个。它是代码执行的最顶层环境。
- 函数执行上下文:每当一个函数被调用时,都会为该函数创建一个新的执行上下文。
- Eval 执行上下文:使用
eval()执行的代码也会创建执行上下文。
执行上下文的生命周期:
-
创建阶段(Creation Phase) :在这个阶段,JavaScript 引擎会进行以下操作:
- 创建 变量对象(Variable Object, VO) 或 活动对象(Activation Object, AO) ,这取决于当前执行上下文是全局还是函数。
- 为每个声明的变量和函数创建 标识符(Identifiers) ,并赋值为
undefined或根据函数初始化。
-
执行阶段(Execution Phase) :执行代码,并根据上下文中定义的变量、函数和
this来运行代码。
2. 调用栈(Call Stack)
调用栈是 JavaScript 引擎管理执行上下文的结构。当一个执行上下文被创建时,它会被推入调用栈。当代码执行完成时,执行上下文会被从调用栈中弹出。
- 栈 是一个后进先出(LIFO)的数据结构,最后加入栈中的元素会最先被移除。
- JavaScript 引擎每次执行一个函数时,都会为该函数创建一个执行上下文,并将其推入栈顶;函数执行完后,执行上下文会从栈顶弹出。
function first() {
second(); // 调用 second 函数
}
function second() {
third(); // 调用 third 函数
}
function third() {
console.log('Hello from third');
}
first(); // 从这里开始
调用栈执行顺序:
first函数被调用,first的执行上下文推入栈。first中调用second,second的执行上下文推入栈。second中调用third,third的执行上下文推入栈。third执行并输出'Hello from third',然后third的执行上下文被弹出。- 返回到
second,second执行完毕后,second的执行上下文被弹出。 - 返回到
first,first执行完毕后,first的执行上下文被弹出。
3. 事件队列(Event Queue)与异步任务
在 JavaScript 中,异步代码的执行通过事件循环机制(Event Loop)进行管理。JavaScript 运行时是单线程的,但是通过事件队列和异步任务,可以在主线程之外执行一些任务。
事件队列和异步任务:
- 事件队列(Event Queue) :用来存储待执行的回调函数队列。
- 异步任务:当异步操作(如
setTimeout、Promise、事件监听等)完成时,其对应的回调函数会被添加到事件队列中。
事件循环(Event Loop)
- 主线程:JavaScript 是单线程的,只有一个主线程来处理同步任务。
- 任务队列:当一个异步任务(例如定时器、
setTimeout)完成时,它的回调函数会被添加到任务队列中,等待主线程空闲时执行。 - 事件循环:事件循环不断检查调用栈是否为空,若为空,则从任务队列中取出一个回调函数并执行。
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 0);
Promise.resolve().then(function() {
console.log('Promise');
});
console.log('End');
输出顺序:
Start
End
Promise
Timeout
4. 微任务与宏任务
JavaScript 中的异步任务分为 宏任务(Macro Tasks)和 微任务(Micro Tasks):
- 宏任务:包括
setTimeout、setInterval、I/O 操作等。 - 微任务:包括
Promise.then、MutationObserver等。
执行顺序:
- 事件循环首先会执行所有同步代码。
- 然后,微任务(比如
Promise)会被执行。 - 最后,宏任务(比如
setTimeout)会被执行。
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 0);
Promise.resolve().then(function() {
console.log('Promise 1');
}).then(function() {
console.log('Promise 2');
});
console.log('End');
输出顺序:
Start
End
Promise 1
Promise 2
Timeout