文章目录:
- JavaScript 代码执行顺序概述
- 1.1 JavaScript 是单线程的
- 1.2 异步编程与事件循环
- 常见问题:为什么代码执行顺序错乱?
- 2.1 同步代码与异步代码的执行顺序
- 2.2
setTimeout和Promise的执行顺序 - 2.3 事件循环机制(Event Loop)
- 实际项目代码示例
- 3.1 示例 1:
setTimeout异步执行 - 3.2 示例 2:
Promise与异步操作 - 3.3 示例 3:
async/await中的执行顺序
- 3.1 示例 1:
- 如何正确理解 JavaScript 的执行顺序
- 4.1 理解执行栈和任务队列
- 4.2 微任务和宏任务的区别
- 总结
1. JavaScript 代码执行顺序概述
1.1 JavaScript 是单线程的
JavaScript 是一种单线程语言,也就是说,它一次只能执行一个任务。这是 JavaScript 执行顺序错乱的根本原因之一。虽然 JavaScript 是单线程的,但它通过 事件循环(Event Loop) 和 异步编程,能够在执行一个任务时同时等待其他任务完成,避免阻塞主线程。
1.2 异步编程与事件循环
在 JavaScript 中,异步编程是通过 setTimeout、Promise、async/await 等方式实现的。这些操作不会立即执行,而是被放入 任务队列 中,等待主线程空闲时执行。JavaScript 使用 事件循环 来管理执行顺序:当主线程完成当前任务时,事件循环会检查任务队列中的异步任务,按顺序执行它们。
2. 常见问题:为什么代码执行顺序错乱?
2.1 同步代码与异步代码的执行顺序
同步代码是按照书写顺序一行一行执行的,而异步代码需要等待特定事件的发生(例如,定时器到期、网络请求返回等)。这就导致了我们通常会遇到 异步操作执行顺序错乱 的情况。
示例:
console.log("开始");
setTimeout(() => {
console.log("异步任务");
}, 0);
console.log("结束");
输出顺序:
开始
结束
异步任务
解释:
"开始"和"结束"是同步代码,直接在栈中执行。setTimeout是异步操作,它的回调函数被放入 任务队列 中,等待事件循环将它从队列中取出并执行。
2.2 setTimeout 和 Promise 的执行顺序
setTimeout 和 Promise 的回调都可以异步执行,但它们的执行顺序是不同的。setTimeout 的回调被放入 宏任务队列,而 Promise 的回调被放入 微任务队列。微任务的优先级高于宏任务,因此会先执行 Promise 的回调。
示例:
console.log("开始");
setTimeout(() => {
console.log("宏任务 - setTimeout");
}, 0);
Promise.resolve().then(() => {
console.log("微任务 - Promise");
});
console.log("结束");
输出顺序:
开始
结束
微任务 - Promise
宏任务 - setTimeout
解释:
"开始"和"结束"是同步代码,直接执行。Promise的回调被放入微任务队列,setTimeout的回调被放入宏任务队列。- 事件循环优先执行微任务(
Promise),然后才执行宏任务(setTimeout)。
2.3 事件循环机制(Event Loop)
事件循环是 JavaScript 处理异步任务的机制。当执行栈为空时,事件循环会从任务队列中获取任务并执行。任务队列有两个主要部分:
- 宏任务队列(Macro Task Queue):例如
setTimeout、setInterval、I/O等。 - 微任务队列(Micro Task Queue):例如
Promise、MutationObserver等。
微任务的优先级高于宏任务,因此它们会在宏任务之前执行。
3. 实际项目代码示例
3.1 示例 1:setTimeout 异步执行
console.log("开始");
setTimeout(() => {
console.log("宏任务 1");
}, 1000);
setTimeout(() => {
console.log("宏任务 2");
}, 0);
console.log("结束");
输出顺序:
开始
结束
宏任务 2
宏任务 1
解释:
"开始"和"结束"是同步代码。- 第一个
setTimeout(延迟 1000 毫秒)被放入宏任务队列。 - 第二个
setTimeout(延迟 0 毫秒)仍然被放入宏任务队列,但它的回调会在主线程空闲后执行,因此它在"结束"后执行。
3.2 示例 2:Promise 与异步操作
console.log("开始");
Promise.resolve().then(() => {
console.log("微任务 1");
}).then(() => {
console.log("微任务 2");
});
setTimeout(() => {
console.log("宏任务 1");
}, 0);
console.log("结束");
输出顺序:
开始
结束
微任务 1
微任务 2
宏任务 1
解释:
"开始"和"结束"是同步代码。Promise的then被放入微任务队列。- 事件循环会在同步代码执行完后优先执行微任务(
微任务 1和微任务 2),然后执行宏任务(宏任务 1)。
3.3 示例 3:async/await 中的执行顺序
async function asyncFunction() {
console.log("开始");
await Promise.resolve(); // 等待 Promise 执行
console.log("等待完成");
setTimeout(() => {
console.log("宏任务");
}, 0);
console.log("结束");
}
asyncFunction();
输出顺序:
开始
结束
等待完成
宏任务
解释:
async函数中的代码会立即执行,直到遇到await。await会暂停函数执行,等待Promise解决,并将控制权交还给事件循环。- 同步代码(
console.log("开始")和console.log("结束"))会先执行。 Promise会被放入微任务队列,setTimeout会被放入宏任务队列。
4. 如何正确理解 JavaScript 的执行顺序
4.1 理解执行栈和任务队列
JavaScript 的执行顺序由以下部分组成:
- 执行栈(Call Stack):用来执行同步代码。
- 宏任务队列(Macro Task Queue):用来存放
setTimeout、setInterval等宏任务。 - 微任务队列(Micro Task Queue):用来存放
Promise、MutationObserver等微任务。
当执行栈为空时,事件循环会检查微任务队列并执行其中的任务。如果微任务队列为空,才会执行宏任务队列中的任务。
4.2 微任务和宏任务的区别
- 微任务:例如
Promise、MutationObserver,它们的执行优先级高于宏任务,在事件循环中微任务队列总是先于宏任务队列执行。 - 宏任务:例如
setTimeout、setInterval、DOM 操作,通常会在微任务执行完毕后执行。
5. 总结
JavaScript 的代码执行顺序可能让初学者感到困惑,尤其是在涉及异步操作时。理解 事件循环、执行栈 和 任务队列 的机制,能帮助你更好地掌握代码执行顺序。异步代码的执行顺序是由事件循环中的微任务和宏任务队列决定的,微任务总是在宏任务之前执行。通过实际项目中的代码示例,我们可以清楚地看到同步与异步代码执行的不同以及事件循环的影响。

被折叠的 条评论
为什么被折叠?



