setInterval和requestAnimationFrame的区别,以及引发的思考

1,292 阅读4分钟

在canvas动画的文章中用到了requestAnimationFrame,丢弃了传统中使用的setInterval

setInterval

以固定的时间间隔,重复运行一段代码.

requestAnimationFrame

setInterval()的现代版本;在浏览器下一次重新绘制显示之前执行指定的代码块,从而允许动画在适当的帧率下运行,而不管它在什么环境中运行. 那么两者的相同之处呢?都是作为Javascript单线程中异步而存在的。

Javascript单线程

进程和线程

进程就好比工厂(GPU)的车间,而车间的工人就是线程。每个车间独立运作,工人协同合作完成工作。
1.一个进程包含多个线程,进程的内存空间是线程共享的
2.进程是相互独立的
3.进程是操作系统分配的最小单位,线程是程序运行最小单位

浏览器中线程

定时器线程

浏览器定时计数器是一个单独的线程,触发定期行为后进入事件触发线程。
假设是在javascript线程,会因为阻塞导致计数不准确。

事件触发线程

当一个事件(鼠标点击、ajax请求、定时器等)触发后,会添加到队列中,等待计js线程处理。

异步http请求线程

浏览器有一个单独的线程用于处理ajax请求,当有回调时,进入事件触发线程

javascript线程

负责处理解析和执行javascript脚本程序

GUI 渲染线程

选择浏览器中html,以及对应的重绘。在javascript线程运行脚本期间,GUI是被暂时挂起的

Event Loop事件循环机制

我最初的理解是:
同步的任务在js线程上执行,形成一个执行栈;setTimeout、setInterval和requestAnimationFrame中的回调作为js中的异步任务,通过触发对应的线程,将回调放入事件队列中;执行栈中的内容执行完成后,GPU会进行重绘,然后js引擎会通过系统去读取事件队列中的内容,将内容放在执行栈中执行。
在实际的过程中呢?
在事件循环的过程中,异步事件返回的结果会被放在事件队列中。根据异步事件的类型,异步事件会被分为宏任务和微任务队列中。执行栈先去执行宏任务,当宏任务中内容为空,主线程会查看当前的微任务是否有事件存在,如果不存在会去执行下一次宏任务,如果存在,先执行当前的微任务。

异步任务解释event-loop。 浏览器中的宏任务主要包含定时器、io操作,js操作,微任务主要是promise、async,requestAnimationFrame姑且作为一种特殊的任务,在执行完微任务后触发。MDN中讲到:告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
首先一个简单的例子:

document.body.style = 'background:blue'
console.log(1)
new Promise(res=>{
  document.body.style = 'background:gray'
  console.log(2)
  res(2)
}).then((r)=>{
  console.log(r)
  document.body.style = 'background:black'
})
console.log(3)
setTimeout(() => {
  console.log(4)
  document.body.style = 'background:pink'
}, 10)

控制台最终效果1、2、3、2、4。
1.执行宏任务中的内容颜色变蓝
2.打印1
3.颜色变灰
4.打印2
5.打印3
6.执行微任务中的打印promise返回结果2
7.颜色变黑
8.GPU渲染
9.执行新的宏任务
10.打印4
11.颜色变粉
浏览器的样式为先黑色在变成粉色,会忽略掉第三个步骤,因为这个时候微任务还没有执行完,所以重绘的时候颜色会直接变成黑色 接下来看一个相对复杂的例子

console.log(1)
setTimeout(() => {
  console.log('setTimeout')
}, 10);
new Promise((res,rej)=>{
  res(11)
  console.log("Promise")
}).then(res=>{
  console.log(res+"PromiseThen")
}).catch(rej=>{

})
window.requestAnimationFrame(()=>{
  console.log('requestAnimationFrame')
})
console.log("hah")
let aa = new Promise((res,rej)=>{
  res(2)
})

async function bb() {
  let a = await aa;
  try {
    console.log(a+"try")
  } catch (error) {
    console.log(a+"catch")
  }
}
bb()
console.log("end")

所以打印结果如下:
1----Promise----hah----end----11PromiseThen----2try----requestAnimationFrame----setTimeout
这里的执行顺序:
1.宏任务开始 打印1 2.触发定时器线程、将回调放入事件队列中
3.promise打印promise,将promise的回调放入当前的微任务中
4.打印hah
5.继续执行promise,将回调放入微任务中
6.打印end
7.执行当前的微任务,打印console.log(res+"PromiseThen")打印11PromiseThen
8.执行当前的微任务,打印console.log(a+"try")打印果2try
9.执行requestAnimationFrameconsole.log('requestAnimationFrame')打印requestAnimationFrame
10.GUP渲染
11.执行下次宏任务定时器中的console.log('setTimeout')打印setTimeout