requestAnimationFrame何时执行?

时间:2017-03-27 15:25:00

标签: javascript asynchronous javascript-events promise requestanimationframe

浏览器读取并运行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);
}

  • 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堆栈变为空,从而阻止当前帧完成(如果微任务也太重)?

1 个答案:

答案 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);
}

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

  • Chrome :microtask,requestAnimationFrame,macrotask
  • Firefox :microtask,macrotask,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);
}

我可靠地在Chrome和Firefox上获得microtask,requestAnimationFrame,macrotask。

  

** Q1:**

     

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

没有任何阻止。

  

** Q2:**

该句子完全,仅仅意味着它所说的内容:在绘制帧之前,将立即调用您的回调(以及排队的任何requestAnimationFrame回调)。 意味着每隔60分钟就必须绘制一个帧 - 因为线程可能正在忙着做其他事情。

那些回调不会中断其他任务。再说一遍:如果其他任务的主UI线程忙,那么它很忙,帧速率也会受损。