浏览器一帧做哪些事情
- js执行
- requestAnimationFrame回调
- 渲染
- requestIdleCallback回调
事件循环主要指的是js执行过程的事件循环
- 宏任务:setTimeout,setInterval,messageChannel,js代码块,输入输出操作,事件回调
- 微任务:Promise.then,.catch,.finally, async await, MutationObverser, queueMicrotask
- 动画队列:Animation callbacks, requestAnimationFrame
上代码
js
console.log('1');
const channel = new MessageChannel();
channel.port1.onmessage = () => {
console.log('2');
setTimeout(() => console.log('3'), 0);
};
channel.port2.postMessage(null);
setTimeout(() => {
console.log('4');
Promise.resolve().then(() => console.log('5'));
}, 0);
async function async1() {
console.log('6');
await async2();
console.log('7');
}
async function async2() {
console.log('8');
await Promise.resolve().then(() => console.log('9'));
console.log('10');
}
Promise.resolve().then(() => {
console.log('11');
});
async1();
new Promise(resolve => {
console.log('12');
resolve();
}).then(() => console.log('13'));
console.log('14');
解析执行过程
同步代码执行
- 输出1
- 产生messageChannel宏任务,放入宏任务队列
- 产生setTimeout宏任务,放入宏任务队列
- 产生.then微任务,放入微任务队列
- 执行async1函数,输出6 , 执行async2函数,输出8,产生一个微任务,放入微任务队列
- 返回继续执行同步代码,输出12, 产生一个微任务,放入微任务队列
- 输出14
同步代码执行完毕,处理微任务队列
- 输出11
- 输出9,产生新的微任务,放入微任务队列
- 输出13
- 将新产生的微任务取出执行,输出10
- 此时async2执行完毕,将saync2后面的代码放入微任务队列
- 取出微任务队列任务执行,输出7
微任务代码全部执行完毕,处理宏任务
- 取出messageChannel宏任务执行,输出2,产生宏任务,放入宏任务队列
- 取出setTimeout的宏任务执行,输出4,产生微任务,放入微任务队列
- 取出微任务执行,输出5、
- 取出宏任务执行,输出3
总结
- messageChannel和setTimeout在浏览器端没有优先级顺序,谁先进入宏任务队列,谁先执行
- async,await的执行,await后面的代码同步执行,await下一行的代码,等到Promise状态变更以后,再放入微任务队列中
- 如果代码中嵌入了requestAnimationFrame回调,那么执行顺序将变得不固定,原因是得看浏览器当前正在处理一帧内的哪个阶段
- 如果是js执行阶段,那么就会先执行setTimeout1和setTimeout2,再输出rAF
- 如果是js执行阶段,并且js执行的剩余时间不太够的情况下,会先输出setTimeout1, 然后输出rAF,再下一帧的js执行阶段,再输出setTimeout2
- 如果当前是requestAnimationFrame阶段,那么会先输入rAF,再下一帧输出setTimeout1和setTimeout2
验证结论1

验证结论3
js
setTimeout(() => {
console.log('setTimeout1');
}, 0);
requestAnimationFrame(() => {
console.log("requestAnimationFrame");
});
setTimeout(() => {
console.log('setTimeout2');
}, 0);
绝大多数情况是

少数情况会出现这样的结果

极少数情况下会出现
