干货 | RAF替代setTimeout_setInterval
Posted 面条请不要欺负汉堡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干货 | RAF替代setTimeout_setInterval相关的知识,希望对你有一定的参考价值。
1. 前言
为什么用 requestAnimationFrame代替 setInterval 和 setTimeout?那得先了解微任务、宏任务与Event-Loop, 还有 setInterval 存在的问题
2. requestAnimationFrame
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,
并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
语法
window.requestAnimationFrame(callback);
- callback 下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传
入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame()开 始去执行回调函数的时刻。 - 返回值 一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给window.cancelAnimationFrame()
以取消回调函数。
3. 了解微任务、宏任务与Event-Loop
4. 运行机制
setTimeout
setTimeout(function()
console.log(1);
);
console.log(0);
实际上,把setTimeout的第二个参数设置为0s,并不是立即执行函数的意思,只是把函数放入异步队列。浏览器先执行完同步队列里的任务,才会去执行异步队列中的任务
在下面这个例子中,给一个按钮btn设置了一个事件处理程序。事件处理程序设置了一个250ms后调用的定时器。点击该按钮后,
首先将onclick事件处理程序加入队列。该程序执行后才设置定时器,再有250ms后,指定的代码才被添加到队列中等待执行
btn.onclick = function()
setTimeout(function()
console.log(1);
,250);
如果上面代码中的onclick事件处理程序执行了300ms,那么定时器的代码至少要在定时器设置之后的300ms后才会被执行。队列中所有
的代码都要等到javascript进程空闲之后才能执行,而不管它们是如何添加到队列中的
setInterval
使用setInterval()的问题在于,定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次,而之间没有任何停顿。
而javascript引擎对这个问题的解决是:当使用setInterval()时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。这确保了定时器代码
加入到队列中的最小时间间隔为指定间隔
但是,这样会导致两个问题**:1、某些间隔被跳过;2、多个定时器的代码执行之间的间隔可能比预期的小**
假设,某个onclick事件处理程序使用setInterval()设置了200ms间隔的定时器。如果事件处理程序花了300ms多一点时间完成,同时定时器代码也花了差不
多的时间,就会同时出现跳过某间隔的情况
例子中的第一个定时器是在205ms处添加到队列中的,但是直到过了300ms处才能执行。当执行这个定时器代码时,在405ms处又给队列
添加了另一个副本。在下一个间隔,即605ms处,第一个定时器代码仍在运行,同时在队列中已经有了一个定时
器代码的实例。结果是,在这个时间点上的定时器代码不会被添加到队列中
5.迭代setTimeout实现setInterval
某些公司会禁用 setInterval,为了避免setInterval()定时器的问题,可以使用链式setTimeout()调用
setTimeout(function fn()
setTimeout(fn,interval);
,interval);
6.模拟setInterval/setTimeout的基本使用
RAF
class RAF
constructor ()
this.init()
init ()
this._timerIdMap =
timeout: ,
interval:
run (type = 'interval', cb, interval = 16.7)
const now = Date.now
let stime = now()
let etime = stime
//创建Symbol类型作为key值,保证返回值的唯一性,用于清除定时器使用
const timerSymbol = Symbol()
const loop = () =>
this.setIdMap(timerSymbol, type, loop)
etime = now()
if (etime - stime >= interval)
if (type === 'interval')
stime = now()
etime = stime
cb()
type === 'timeout' && this.clearTimeout(timerSymbol)
this.setIdMap(timerSymbol, type, loop)
return timerSymbol // 返回Symbol保证每次调用setTimeout/setInterval返回值的唯一性
setIdMap (timerSymbol, type, loop)
const id = requestAnimationFrame(loop)
this._timerIdMap[type][timerSymbol]= id
setTimeout (cb, interval) // 实现setTimeout 功能
return this.run('timeout', cb, interval)
clearTimeout (timer)
cancelAnimationFrame(this._timerIdMap.timeout[timer])
setInterval (cb, interval) // 实现setInterval功能
return this.run('interval', cb, interval)
clearInterval (timer)
cancelAnimationFrame(this._timerIdMap.interval[timer])
使用
var raf = new RAF()
var timer1 = raf.setInterval(() =>
console.log(1000)
, 1000)
var timer2 = raf.setInterval(() =>
console.log(1500)
, 1500)
raf.setTimeout(() =>
raf.clearInterval(timer1)
raf.clearInterval(timer2)
, 6000)
7. 计算计算机刷新频率
// 题外计算一下 requestAnimationFrame 刷新频率
let startTime = Date.now()
let timer = null
let arr = []
function calc ()
timer = requestAnimationFrame(calc)
const nowTime = Date.now()
const diff = nowTime - startTime
startTime = nowTime
arr.push(diff)
calc()
setTimeout(() =>
const newArr = arr.filter(v => v > 10)
console.log(newArr)
const total = newArr.reduce((t, i) => t + i, 0)
console.log('平均刷新次数', 1000 / (total / newArr.length)) // 平均刷新次数 60.00670465973852
cancelAnimationFrame(timer)
, 3000)
8.引用
requestAnimationFrame
深入理解定时器系列第一篇——理解setTimeout和setInterval
RAF替代setTimeout/setInterval
以上是关于干货 | RAF替代setTimeout_setInterval的主要内容,如果未能解决你的问题,请参考以下文章
java-RandomAccessFile操作以及IO流简单使用