简述Js的事件循环
普通消息队列
一个代码块中的所有同步代码,都会被看作一个宏任务,添加到普通消息队列的尾部
延迟执行队列
当你在代码中使用 setTimeout 或 setInterval 时,会创建一个宏任务
延迟指定的时间后,将其放入延迟执行队列的尾部
事件循环系统
为了简化叙述,我们可以理解为:每一轮事件循环都执行一个宏任务,每一轮事件循环都关联一个微任务队列
具体过程如下:
- 检查普通消息队列是否为空,若不为空,则从队列头部取出一个宏任务执行;否则,从延迟执行队列头部取出一个宏任务执行
- 执行过程中如果遇到 setTimeout 或 setInterval,延迟指定时间后,将其作为一个宏任务 添加到延迟执行队列的尾部
- 执行过程中如果创建了微任务(Promise 或者 MutationObserver),就将它添加到当前的微任务队列中
- 执行当前微任务队列中的所有微任务
- 开始下一个循环
例题1:
console.log('--开始--');
setTimeout(() => {
console.log('timer1');
}, 0);
new Promise((resolve, reject) => {
for (let i = 0; i < 5; i++) {
console.log(i);
}
resolve()
}).then(()=>{
console.log('Promise');
})
console.log('--结束--');
//---开始--
//0
//1
//2
//3
//4
//--结束--
//Promise
//timer1
第一轮循环:普通消息队列中只有一个宏任务(整个script)
- 遇到console.log,直接输出 "--开始--"
- 遇到setTimeout,因为指定时间为0ms,所以立即将其回调函数timer1添加到延迟执行队列的尾部
- 遇到Promise的executor,直接执行,依次输出0、1、2、3、4,遇到resolve,将其回调函数添加到当前的微任务队列中
- 遇到console.log,直接输出 “--结束--”
- 检查微任务队列,发现一个微任务,立即执行,输出“Promise”
第二轮循环:普通消息队列为空,延迟执行队列中有一个宏任务(timer1)
- 执行timer1的回调函数
- 发现微任务队列为空,本轮循环结束
例题2:
new Promise((resolve) => {
console.log('this is executor');
resolve();
}).then(() => {
console.log('this is resolve');
setTimeout(() => { // 命名为timer1
console.log('timer1');
}, 4)
})
setTimeout(() => { // 命名为timer2
console.log('timer2');
}, 4)
console.log('-- 结束--');
//this is executor
//-- 结束--
//this is resolve
//timer2
//timer1
第一轮循环:普通消息队列中只有一个宏任务(整个script)
- 遇到Promise的executor,直接执行,输出“this is executor”
- 遇到resolve,将其回调函数添加到当前的微任务队列中
- 遇到setTimeout,在4ms后将timer2添加到延迟执行队列的尾部
- 遇到console.log,直接输出 “--结束--”
- 检查微任务队列,发现一个微任务,立即执行
- 遇到console.log,直接输出 “this is resolve”
- 遇到setTimeout,在4ms后将timer1添加到延迟执行队列的尾部,本轮循环结束
第二轮循环:普通消息队列为空,延迟执行队列头部为timer2
- 执行timer2,输出timer2
第三轮循环:延迟执行队列头部为timer1
- 执行timer1,输出timer1