你应该知道的requestIdleCallback

咱们都知道React 16实现了新的调度策略(Fiber), 新的调度策略提到的异步、可中断,其实就是基于浏览器的 requestIdleCallback和requestAnimationFrame两个API。因此这里咱们有必要了解一下这两个API,关于Fiber部分后面会单开几篇讲。react

什么是requestIdleCallback?

当关注用户体验,不但愿由于一些不重要的任务(如统计上报)致使用户感受到卡顿的话,就应该考虑使用requestIdleCallback。由于requestIdleCallback回调的执行的前提条件是当前浏览器处于空闲状态。web

requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive.npm

requestIdleCallback用法示例浏览器

requestIdelCallback(myNonEssentialWork);
    
    
    function myNonEssentialWork (deadline) {
    
      // deadline.timeRemaining()能够获取到当前帧剩余时间
      while (deadline.timeRemaining() > 0 && tasks.length > 0) {
        doWorkIfNeeded();
      }
      if (tasks.length > 0){
        requestIdleCallback(myNonEssentialWork);
      }
    }
复制代码

requestIdleCallback和requestAnimationFrame有什么区别?

requestAnimationFrame的回调会在每一帧肯定执行,属于高优先级任务,而requestIdleCallback的回调则不必定,属于低优先级任务。 咱们所看到的网页,都是浏览器一帧一帧绘制出来的,一般认为FPS为60的时候是比较流畅的,而FPS为个位数的时候就属于用户能够感知到的卡顿了,那么在一帧里面浏览器都要作哪些事情呢,以下所示:bash

图中一帧包含了用户的交互、js的执行、以及requestAnimationFrame的调用,布局计算以及页面的重绘等工做。 假如某一帧里面要执行的任务很少,在不到16ms(1000/60)的时间内就完成了上述任务的话,那么这一帧就会有必定的空闲时间,这段时间就刚好能够用来执行requestIdleCallback的回调,以下图所示:dom

因为requestIdleCallback利用的是帧的空闲时间,因此就有可能出现浏览器一直处于繁忙状态,致使回调一直没法执行,这其实也并非咱们指望的结果(如上报丢失),那么这种状况咱们就须要在调用requestIdleCallback的时候传入第二个配置参数timeout了?异步

requestIdleCallback(myNonEssentialWork, { timeout: 2000 });

function myNonEssentialWork (deadline) {
  // 当回调函数是因为超时才得以执行的话,deadline.didTimeout为true
  while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
         tasks.length > 0) {
       doWorkIfNeeded();
    }
  if (tasks.length > 0) {
    requestIdleCallback(myNonEssentialWork);
  }
}
复制代码

若是是由于timeout回调才得以执行的话,其实用户就有可能会感受到卡顿了,由于一帧的执行时间必然已经超过16ms了函数

requestIdleCallback里面能够执行DOM修改操做吗?

强烈建议不要,从上面一帧的构成里面能够看到,requestIdleCallback回调的执行说明前面的工做(包括样式变动以及布局计算)都已完成。若是咱们在callback里面作DOM修改的话,以前所作的布局计算都会失效,并且若是下一帧里有获取布局(如getBoundingClientRect、clientWidth)等操做的话,浏览器就不得不执行强制重排工做,这会极大的影响性能,另外因为修改dom操做的时间是不可预测的,所以很容易超出当前帧空闲时间的阈值,故而不推荐这么作。推荐的作法是在requestAnimationFrame里面作dom的修改,能够在requestIdleCallback里面构建Document Fragment,而后在下一帧的requestAnimationFrame里面应用Fragment。布局

除了不推荐DOM修改操做外,Promise的resolve(reject)操做也不建议放在里面,由于Promise的回调会在idle的回调执行完成后马上执行,会拉长当前帧的耗时,因此不推荐。性能

推荐放在requestIdleCallback里面的应该是小块的(microTask)而且可预测时间的任务。关于microTask推荐看这里

requestIdleCallback的兼容状况

推荐使用npm包request-idle-callback

参考资料

https://developers.google.com/web/updates/2015/08/using-requestidlecallback https://medium.com/@paul_irish/requestanimationframe-scheduling-for-nerds-9c57f7438ef4 https://juejin.im/entry/59082301a22b9d0065f1a186 https://insights.thoughtworks.cn/react-fiber/