干货 | 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

微任务_宏任务与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的主要内容,如果未能解决你的问题,请参考以下文章

javascript RAF-polyfill.js

upc 3025 Fleecing the Raf?e

java-RandomAccessFile操作以及IO流简单使用

艺术家Sterling Ruby为何让Raf Simons流下热泪 | 时装

requestAnimationFrame

【干货】12 款 Linux 终端推荐