JavaScript
是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环。
JavaScript 在浏览器中的执行环境(也就是 JS 引擎)是单线程的,这个线程叫 主线程,主要负责:
- 执行代码(同步、异步回调等)
- 操作 DOM
- 执行事件循环
JS 主线程不断的循环往复的从任务队列中读取任务,执行任务,这种运行机制称为事件循环(event loop) 事件循环(Event Loop)是 JavaScript 的执行机制,它确保同步代码优先执行,异步任务在适当的时机被处理。
JavaScript 是单线程语言,主线程首先执行所有同步任务。遇到异步任务(如定时器、网络请求、事件监听等),会将它们交给浏览器或 Node.js 的其他线程处理(如 Web APIs)。当这些异步操作完成后,相应的回调函数会被放入任务队列中。
事件循环机制不断从任务队列中取出任务执行。每次循环称为一次“tick”,每个 tick 中:
- 执行一个宏任务(如
setTimeout
、setInterval
、I/O
、MessageChannel
)。 - 在该宏任务执行完后,会立即清空微任务队列(如
Promise.then
、MutationObserver
、queueMicrotask
)。
然后进入下一次循环,继续取出下一个宏任务,如此反复。
注意:不是先执行微任务,而是先执行一个宏任务,然后在该宏任务结束后立即执行所有微任务。
正确的执行顺序:
- 从宏任务队列中取出一个宏任务执行(比如:整体脚本script,setTimeout回调等,每次执行一个)
- 执行过程中产生的所有微任务(比如Promise.then)会被加入微任务队列
- 在这个宏任务执行完成之后,立即执行所有微任务队列中的任务
- 然后进入下一轮的事件循环,重复这个过程,直到所有的宏任务执行完成。
参考文章:js事件循环流程图 流程图模板_ProcessOn思维导图、流程图
练习题1
console.log("start");
setTimeout(() => {
console.log("children2")
Promise.resolve().then(() =>{
console.log("children3")
}) // 成功后的回调 属于微任务
}, 0)
new Promise(function(resolve, reject){
console.log("children4")
setTimeout(function(){
console.log("children5")
resolve("children6")
}, 0)
}).then(res =>{ // flag
console.log("children7")
setTimeout(() =>{
console.log(res)
}, 0)
})
第一轮:
- 先输出宏任务(script脚本)
- 遇到同步代码,执行start。
- 遇到异步代码,丢进宏任务队列。
- 遇到Promise立即执行,输出children4。
- 又遇到一个
setTimeout
直接又丢入到宏任务队列,第一轮宏任务执行完,且没有微任务
问:上面的 .then()
(注释的flag
处) 是第一轮宏任务循环的微任务吗?
不是!因为resolve
都没有执行,promise
的状态都还没有从pending
改变,就不是第一轮的微任务。
第二轮:
先输出宏任务(setTimeout)输出children2
,第二轮宏任务结束
开始微任务执行promise
中的.then()
输出 children3
第二轮循环结束
第三轮:
接着又开始setTimeout
的宏任务,输出children5
,微任务输出 children7
。这里遇到一个宏任务 setTimeout
,丢入宏任务队列。第三轮循环结束
第四轮:又开始新 setTimeout
宏任务,输出 res children6
等价写法
练习题2
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1()
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
✅ 首先明确:
-
一轮事件循环(tick)包括:
- 取出一个宏任务(宏任务队列中)
- 执行它的全部代码
- 执行当前产生的所有微任务
- 然后进入下一轮事件循环
🧠 事件循环分析:
✅ 第 1 轮事件循环(主线程) :宏任务 = 整个同步代码
执行顺序:
-
console.log('script start')
→ ✅ -
注册
setTimeout
→ 宏任务入队 ✅ -
async1()
执行:-
输出:
async1 start
-
await async2()
:- 执行
async2()
→ 输出:async2
await
之后的console.log('async1 end')
→ 加入微任务队列 ✅
- 执行
-
-
创建 Promise:
- 输出:
promise1
then(...)
→ 加入微任务队列 ✅
- 输出:
-
console.log('script end')
→ ✅
🔹 同步任务结束,本轮开始执行微任务队列:
- 输出:
async1 end
- 输出:
promise2
⏱️ 微任务执行完毕,第一轮事件循环结束 ✅
✅ 第 2 轮事件循环:宏任务 = setTimeout
- 输出:
setTimeout
执行完,没有新的微任务,结束。
✅ 最终结论:
总共执行了 2 轮事件循环:
1️⃣ 第 1 轮:主脚本(同步 + 微任务)
2️⃣ 第 2 轮:setTimeout 回调(宏任务)
练习题3: `
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise3');
resolve();
}).then(function () {
console.log('promise4');
});
console.log('script end');
//script start,
// async1 start,
// promise1,
// promise3,
// script end,
// promise2,
// async1 end,
// promise4,
// setTimeout