在页面空闲的时候,执行一些计算或某些特殊的后台任务,这样可以避免对用户操作带来卡顿的问题。虽然js支持异步编程,但是即使某些任务是异步执行的,但是因为js是单线程程序,所以,如果一个任务需要花费比较长的时间去进行计算,那么即使它是在异步回调的时候执行,也会带来界面卡顿,而如果用户在这个卡顿期间进行交互操作,就会明显感到卡死状态,体验不好。
有没有一种办法避免这种情况发生?当然有,最好的方式是使用webworker,启用另外一个线程去执行这个需要消耗大量时间的运算。因为webworker和用户界面所在的线程相互不影响,所以,不会给用户带来卡顿感。当计算完毕之后,通过postMessage实现数据传递,对于用户而言,几乎无感。
但是在一些特殊情况下,我们需要在用户界面所在的主线程去执行这种程序。比如,我们要在这个任务中获取DOM的一些信息。这个时候,我们要想办法让这个任务不对用户的操作造成影响。
web标准提供了requestIdleCallback这个接口,它的用法有点像requestAnimationFrame,它主要用于在浏览器闲时执行某个任务。比如:
var a = 0 requestIdleCallback(() => { a ++ })
上面这段代码,给浏览器下了一个命令,当浏览器空闲的时候,执行a ++。
不过requestIdleCallback会有一些兼容性问题,我们只能通过一些手段来使它在低版本浏览器可以用:
export const requestIdleCallback = window.requestIdleCallback || function(cb, delay = 1000) { const start = Date.now() const action = () => cb({ didTimeout: false, timeRemaining: function() { return Math.max(0, 50 - (Date.now() - start)) }, }) const id = setTimeout(() => { timeout.id = setTimeout(action, delay) timeout.reset = () => { clearTimeout(timeout.id) timeout.id = setTimeout(action, delay) } document.addEventListener('keydown', timeout.reset, true) document.addEventListener('mousedown', timeout.reset, true) document.addEventListener('touchstart', timeout.reset, true) document.addEventListener('touchmove', timeout.reset, true) document.addEventListener('mousemove', timeout.reset, true) window.addEventListener('scroll', timeout.reset, true) window.addEventListener('resize', timeout.reset, true) }) var timeout = { id, reset: null } return timeout } export const cancelIdelCallback = window.cancelIdelCallback || function(timeout) { if (!timeout) { return } clearTimeout(timeout.id) document.removeEventListener('keydown', timeout.reset) document.removeEventListener('mousedown', timeout.reset) document.removeEventListener('touchstart', timeout.reset) document.removeEventListener('touchmove', timeout.reset) document.removeEventListener('mousemove', timeout.reset) window.removeEventListener('scroll', timeout.reset) window.removeEventListener('resize', timeout.reset) timeout.reset = null timeout.id = null }
cancelIdelCallback用于取消前面下达的命令,参数是requestIdleCallback的返回值。
不过有一个问题,就是requestIdleCallback是只执行一次的,如果我们想要做一个守护程序,在浏览器空闲的时候,就开始运行这个守护程序,应该怎么做呢?
/** * 创建一个在空闲时执行的任务 * @param {*} fn */ export function autoidle(fn, interval = 1000, immediate = true) { var idle const run = () => { const action = () => { idle = requestIdleCallback(run, interval) } asyncx(fn)().then(action).catch(action) } const start = () => { cancelIdelCallback(idle) idle = requestIdleCallback(run, interval) } const stop = () => { cancelIdelCallback(idle) } if (immediate) { start() } return { start, stop } }
这个函数可以创建一个守护程序,它可以在你的浏览器空闲的时候不断运行,但你的浏览器开始忙碌的时候,又不会运行的效果。另外,它返回两个函数,start 和 stop,用以在必要的时候停止和重启任务。
技术人员的文章看不懂。