requestAnimationFrame何时执行?

Shu*_*tsu 10 javascript asynchronous javascript-events promise requestanimationframe

浏览器读取并运行JavaScript文件,文件中写入的同步任务立即变为执行中任务,setTimeout回调变为macrotasks,并且promise回调变为微任务.一切都是好的.

在我见面之前,我以为我掌握了JavaScript事件循环requestAnimationFrame.

@TJ 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);
}
Run Code Online (Sandbox Code Playgroud)

  • Chrome:microtask,requestAnimationFrame,macrotask
  • Firefox:microtask,macrotask,requestAnimationFrame

规范没有说明在完成宏任务和处理其预定的微任务之间是否会发生这种情况,或者只是在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堆栈变为空,从而阻止当前帧完成(如果微任务也太重)?

T.J*_*der 6

这基本上是自己的事情。当浏览器即将重新绘制页面时,如果没有被正在运行的任务阻止,它通常每秒执行60次,它将requestAnimationFrame在此之前调用所有排队的回调,然后重新绘制。

规范没有说明这是否可以在宏任务完成和计划的微任务处理之间发生,还是只能在宏任务之间发生。因此,可能会因浏览器而异。

旧的规范(现在已过时且已被取代)以宏任务的术语进行了描述,暗示它应该在宏任务之间,但是事情可能从那里继续发展。

让我们做一个测试:

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);
}
Run Code Online (Sandbox Code Playgroud)

当然,结果因浏览器而异:

  • Chrome:微任务,requestAnimationFrame,宏任务
  • Firefox:微任务,宏任务,requestAnimationFrame

(在这些浏览器上进行反复测试后,我可靠地获得了相同的结果。我没有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);
}
Run Code Online (Sandbox Code Playgroud)

有了这一更改,我在Chrome和Firefox上都可靠地获得了微任务,requestAnimationFrame,宏任务。


**第一季度:**

即使浏览器没有重绘工作,requestAnimationFrame也会以刷新率(默认为每秒60次)执行的回调。

只要没有阻碍。

**第二季度:**

该句子仅表示其意思:requestAnimationFrame绘制框架之前,将立即调用您的回调(以及排队的所有回调)。这并不意味着必须每60秒绘制一帧,因为线程可能正在忙于做其他事情。

这些回调不会中断其他任务。再次:如果其他任务的主UI线程处于繁忙状态,则该线程处于繁忙状态,并且帧速率受损。

  • @Vanis:帧之间的“间隔”通常为 16.67 毫秒,前提是浏览器每秒绘制 60 帧,这可能是也可能不是——不活动的选项卡往往会变慢甚至停止(启动 [this fiddle] 很有趣) (https://jsfiddle.net/mosdxrhk/),然后使选项卡处于非活动状态,然后返回到它),或者某些任务可能会占用 UI 线程。当然,如果没有任何待处理的“requestAnimationFrame”回调,浏览器可能不需要重新绘制任何内容(但此时我们并不真正关心)。 (2认同)