事件处理和屏幕更新是用户关注性能最明显的两种方式,防止事件队列中出现卡顿是很重要的。可以充分利用浏览器渲染帧的空余时间,既不会导致系统延迟,也有助于浏览器的事件循环平稳运行
浏览器的 Event Loop
同步任务
在主线程上排队执行的任务,只有当前任务执行完毕,才会执行下一个任务
异步任务
不进入主线程,而是进入 “任务队列” 的任务,当主线程空闲的时候去读取执行
宏任务(macro):setInterval()、setTimeout()、fetch、XMLHttpRequest
微任务(micro):MutationObserver、promise.then()
注意:微任务会优先宏任务执行,只有微任务队列空了才会去执行下一个宏任务;new Promise 进入主线程中立刻执行,而 promise.then() 则属于微任务
浏览器的渲染帧
浏览器的事件循环 Event Loop 也是浏览器渲染帧里的一部分
当前渲染帧所花费的时间小于 16.67ms 的时候,浏览器会产生空闲时间,即 rIC
至于 16.67ms 是怎么得出来的,可阅读 第15期 - 巧用浏览器的页面绘制周期 一文
幕后任务协作调度 API
-
利用浏览器渲染帧内的空闲时间
const handle = window.requestIdleCallback(callback, { timeout: 2000 }) const callback = ({didTimeout, timeRemaining} = deadline) => { // 当前帧有剩余时间或者超时的时候 while(timeRemaining() > 0 || didTimeout) { // 指定的任务 } }如果一直没有空闲时间,在超过
timeout设置的时间后,则会强制执行 -
优雅降级
window.requestIdleCallback = window.requestIdleCallback || function(handler) { let startTime = Date.now() return setTimeout(() => { handler({ didTimeout: false, timeRemaining: () => Math.max(0, 50.0 - (Date.now() - startTime)) }) }, 1) }window.cancelIdleCallback = window.cancelIdleCallback || function(id) { clearTimeout(id); }回退到
setTimeout(),限制每次传递的运行时间不超过50毫秒,来兼容不支持后台任务API的浏览器
注意事项
-
避免在空闲回调中执行占用时间不可预测的任务
事件循环有可能会用尽所有的可用时间,虽然设置
timeout可以保证代码按时执行,但是在剩余时间不足以强制执行代码的时候,就会造成页面卡顿或者动画不流畅 -
避免在空闲回调中操作DOM
空闲回调执行的时候,当前帧已经结束绘制,所有布局的更新和计算也已经完成。如果你需要在回调中改变 DOM,应该使用
window.requestAnimationFrame()来调度它一起学习,加群交流看 沸点