浏览器读取并运行JavaScript文件,文件中写入的同步任务立即变为执行中任务,setTimeout回调变为macrotasks,并且promise回调变为微任务。一切都很好。
在我遇到requestAnimationFrame
之前,我以为我掌握了JavaScript事件循环。
@ T.J。 Crowder为我提供了以下代码片段。
const messages = [];
setTimeout(() => {
// Schedule a microtask
Promise.resolve().then(() => {
log("microtask");
});
// Schedule animation frame callback
requestAnimationFrame(() => {
log("requestAnimationFrame");
});
// Schedule a macrotask
setTimeout(() => {
log("macrotask");
}, 0);
// Schedule a callback to dump the messages
setTimeout(() => {
messages.forEach(msg => {
console.log(msg);
});
}, 200);
// Busy-wait for a 10th of a second; the browser will be eager to repaint when this task completes
const stop = Date.now() + 100;
while (Date.now() < stop) {
}
}, 100);
function log(msg) {
messages.push(Date.now() + ": " + msg);
}
规范没有说明在完成宏任务和处理其预定的微任务之间,还是仅在macrotasks之间发生这种情况。所以大概可以改变浏览器到浏览器。
但是在Chrome和Firefox中,微任务总是在requestAnimationFrame
回调之前执行。我的问题基于这一观察结果。
** Q1:**
即使浏览器没有重绘工作,requestAnimationFrame的回调是否会以刷新率执行(默认为每秒60次)?
** Q2:**
以下是https://developers.google.com/web/fundamentals/performance/rendering/debounce-your-input-handlers
保证JavaScript在框架开始时运行的唯一方法是使用
requestAnimationFrame
。
执行中的执行任务过重会滞后浏览器,导致帧间隔超过16.66ms,阻止帧完成。
“保证”一词是否意味着微任务将立即执行,当前的JS堆栈变为空,从而阻止当前帧完成(如果微任务也太重)?
答案 0 :(得分:4)
这基本上是它自己的事情。当浏览器即将重新绘制页面时,如果没有被正在运行的任务阻止,它通常会执行60次/秒,它会在执行此操作之前调用所有排队的requestAnimationFrame
回调,然后进行重新绘制。
The spec没有说明在完成宏任务和处理其预定的微任务之间,还是只在macrotasks之间发生这种情况。所以大概可以改变浏览器到浏览器。
The old spec(现已过时和取代)用macrotask术语描述它,暗示它将介于macrotask之间,但事情可能已经从那里开始。
让我们做一个测试:
const messages = [];
setTimeout(() => {
// Schedule a microtask
Promise.resolve().then(() => {
log("microtask");
});
// Schedule animation frame callback
requestAnimationFrame(() => {
log("requestAnimationFrame");
});
// Schedule a macrotask
setTimeout(() => {
log("macrotask");
}, 0);
// Schedule a callback to dump the messages
setTimeout(() => {
messages.forEach(msg => {
console.log(msg);
});
}, 200);
// Busy-wait for a 10th of a second; the browser will be eager to repaint when this task completes
const stop = Date.now() + 100;
while (Date.now() < stop) {
}
}, 100);
function log(msg) {
messages.push(Date.now() + ": " + msg);
}
果然,结果因浏览器而异:
(我在这些浏览器上重复测试时可靠得到相同的结果。我没有Edge方便......)
这是一个版本,其中忙碌等待预先,而不是在最后,以防它发生变化:
const messages = [];
setTimeout(() => {
// Busy-wait for a 10th of a second; the browser will be eager to repaint when this task completes
const stop = Date.now() + 100;
while (Date.now() < stop) {
}
// Schedule a microtask
Promise.resolve().then(() => {
log("microtask");
});
// Schedule animation frame callback
requestAnimationFrame(() => {
log("requestAnimationFrame");
});
// Schedule a macrotask
setTimeout(() => {
log("macrotask");
}, 0);
// Schedule a callback to dump the messages
setTimeout(() => {
messages.forEach(msg => {
console.log(msg);
});
}, 200);
}, 100);
function log(msg) {
messages.push(Date.now() + ": " + msg);
}
我可靠地在Chrome和Firefox上获得microtask,requestAnimationFrame,macrotask。
** Q1:**
即使浏览器没有重绘工作,
requestAnimationFrame
的回调也会以刷新率(默认为每秒60次)来执行。
没有任何阻止。
** Q2:**
该句子完全,仅仅意味着它所说的内容:在绘制帧之前,将立即调用您的回调(以及排队的任何requestAnimationFrame
回调)。 不意味着每隔60分钟就必须绘制一个帧 - 因为线程可能正在忙着做其他事情。
那些回调不会中断其他任务。再说一遍:如果其他任务的主UI线程忙,那么它很忙,帧速率也会受损。