js API之requestIdleCallback
什么是requestIdleCallback
window.requestIdleCallback() 方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。
你可以在空闲回调函数中调用 requestIdleCallback(),以便在下一次通过事件循环之前调度另一个回调。
以上是MDN对requestIdleCallback的解释,那我们来细说一下这个api他的实际应用和用法。
触发时机
MDN对requestIdleCallback的解释中说到当浏览器空闲的时候函数会被调用,那什么是浏览器的空闲时间呢?
第一种出发条件
首先我们要知道FPS:通俗的来讲就是指画面的刷新率,其实无论游戏、电影、电视等视觉画面都是一张张连续变化的图片快速替换构成。60FPS就是1秒钟替换60次图片,达到60FPS人眼看起来是很流畅、连贯的画面了,所以普通显示器的刷新率就是60FPS。
我们以60FPS为例:
1s = 1000ms (1s等于1000毫秒)
1000 / 60 = 16.6666666 (一帧所用的时间大概就是 16.6ms)
在这一帧当中浏览器做了很多事情
- 处理用户事件,例如:click、input change等。
- 执行定时任务。
- 执行requestAnimationFrame
- 执行dom的回流与重绘
- 计算更新图层的绘制指令
- 绘制指令合并主线程若有空余时间会执行requestIdleCallback
一边情况下requestIdleCallback进入到上述情况下会很少,毕竟浏览器在一帧内执行了很多事情,所以更多情况下会在一下情况下触发
第二种触发条件
没有任务执行浏览器会有50ms空闲时间,这个时间段也会执行requestIdleCallback,大部分都是这个条件下去触发的。
requestIdleCallback(funciton(deadline){
console.log(deadline.timeRemaining())
})
我们可以使用以上方法去测试它调用的时机,我们创建一个index.html,加入这段代码,你会发现这段代码会被console.log 稳定在16.6以内,说明是第一种方式调用。细心的你这里就会问了,你不是是说常常都是第二种方式触发么?秋豆马跌,这是因为我们index.html中什么业务逻辑也没有导致,想象如果把它放到我们的实际业务中去那肯定是第二种方式触发。
其实我们也可以测试,直接把这段代码放到控制台运行一下试试,你会发现console出了49.9 不超过50
这个东西你会发现和react的fiber有很大的联系,react是模仿requestIdleCallback的执行机制去实现了一个自己的api。react之所以不直接使用requestIdleCallback是因为react在调研requestIdleCallback的时候发现有概率出现20ms执行,超出了16.6ms这样的话我们看起来的动画就会卡顿。
React是如何实现的requestIdleCallback的呢?
- React16 使用的是postMessage + requestAnimationFrame 实现了requestIdleCallback的polyfill。
那为什么使用postMessage而不用setTimeout呢?是因为setTimeout 0是4ms,还是有延迟的,postMessage无延迟。 - React 18 使用MessageChannel 实现了requestIdleCallback的polyfill
// 起初这个api是用于iframe通讯和window.open通讯。
let {port1, port2} = new MessageChannel();
// port1可以给port2发消息,2也可以给1发
// onmessage会隐性的开启 port1.start(),正常来说需要先start()
port1.onmessage = function(e){
// e是MessageChannel的实例
console.log('收到了来自port2的消息', e)
}
port2.onmessage = function(e){
// e是MessageChannel的实例
console.log('收到了来自port1的消息', e)
}
port1.postMessage('我是port1');
port2.postMessage('我是port2');
总结
我们项目中如果需要处理一些预加载资源的时候可以使用这个api,好多三方的库在做预加载的时候都会去使用requestIdleCallback。比如微前端中我们经常使用的无界,他的懒加载模块实现原理就是使用了requestIdleCallback API。