rAF在EventLoop的表现
最近会被问到EventLoop的相关问题,这个只要对任务微任务理解到位一般没啥问题,但有次被问到“requestAnimation的执行时机是什么”,答约:“rAF是在浏览器重新渲染屏幕之前执行”,之后被追问:“那它属于宏任务还是微任务,他在时间循环的执行时机是什么样的”。
当时听到这个问题一时有点懵,因为按照之前的经验和理解,rAF一般做动画场景很少遇到宏任务微任务的问题,我感觉它跟宏任务微任务是没啥关系的,因为我看过MDN HTML5的规范关于‘Task’和‘Microtask’的划分,是没看到它的身影的。下来后研究了下,比如下面这段代码,大家可以猜猜输出什么:
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
});
new Promise((resolve) => {
console.log(4);
resolve();
})
.then(() => {
console.log(5);
return 6;
})
.then(Promise.resolve(7))
.then((res) => {
console.log(res);
});
// setTimeout(() => {
// console.log('setTimeout2');
// });
requestAnimationFrame(() => {
console.log('animation’');
});
你输入到浏览器,看到输出可能是:
4 2 5 3 6 animation setTimeout
这时候你是不是就把rAF归为微任务了?那你再执行的同时,滚动下浏览器,会发现输出变不一样了,变成了:
4 2 5 3 6 setTimeout animation
此时把注释打开,再加一个宏任务,执行同时滚动浏览器,发现输出
4 2 5 3 6 setTimeout setTimeout2 animation
这时,你就会发现rAF的表现又有点像last task的表现。
对这种现象,我归结表现为:
- 如果不滚动屏幕,不需要重新绘制帧,requestAnimationFrame会在下一个宏任务之前伴随着本轮微任务执行(顺序也会按照微任务队列的顺序),,即输出'setTimeout','animation'
如果发生了滚动事件,需要重新绘制屏幕,animation最后执行.就算再加一个setTimeout宏任务,还是同理的表现
如果发生了滚动或者重绘操作,定时器和raF的执行会变得不固定
取决于当前的loop是不是render loop,可以简单理解,当前的loop是不是到了16ms且是否有raf回调。
查了不少资料,国内的很多要么没解释清楚要么瞎解释,外网stackOverFlow上有一些讨论,国内的推荐看这篇 链接,感觉还是要朝着源码和规范去理解,但其实最后浏览器差异和版本的差异表现还有不同(比如rIC的执行在chrome不同版本的执行。。)。