【js事件循环】+ requestAnimationFrame与页面绘制在事件循环中的顺序关系

事件循环是啥

学js的都知道,js是单线程的,由js引擎线程单独运行,所以它的性质决定在js(除web worker,和nodejs多进程)里,不会有真正的异步出现,连并发都不是,而是以事件轮询的方式执行微任务和宏任务:

举个栗子:

setTimeout(()=>{
	console.log(1)
},0)
new Promise( re =>{
	console.log(2)
	re(3)
}).then(val=>{
	console.log(val)
})
console.log(4)

结果顺序输出 2 4 3 1
其中setTimeout是宏任务,promise是微任务,promise里的内容会立即执行,但是then方法指定的回调函数会在脚本同步任务执行完毕之后执行,而setTimeout等宏任务会等微任务和同步任务执行完毕之后最后执行。

策略是轮询,先把同步任务调入主执行栈,执行完先找微任务,有就将微任务调入执行栈执行,没有就找宏任务。所以js中的异步并不是并发,而是滞后。
下面我们来看看页面绘制与这些任务的顺序关系。

第一帧重绘与promise的顺序

我们先来看一下代码的执行顺序。

	  const div = document.querySelector('div')
      div.addEventListener('click', function() {
        console.log(1)
        this.style.backgroundColor = 'black'
        Promise.resolve(1).then(() => {
          while (true) {
            console.log('promise')
          }
        })
      })

我们预期是先执行同步的this.style.backgroundColor = 'black'将div设置成为黑色,然后再执行promise里面的死循环。但最终结果是控制台先输出1,然后再一直输出promise,并且div永远不会变成黑色,提前被promise里面的死循环阻塞了。也就是说,页面重绘是滞后于微任务的。
我们再进行一个试验

页面重绘与微任务、宏任务的顺序

	  const div = document.querySelector('div')
      div.addEventListener('click', function() {
        setTimeout(() => {
          while (true) {
            console.log('setTimeout')
          }
        }, 0)
        this.style.backgroundColor = 'black'
      })

结果是div成功变成了黑色,控制台不停地输出setTimeout,所以总地来说,页面重绘是夹在微任务与宏任务中间。
我们再来看一下requestAnimationFrame

requestAnimationFrame与页面重绘的顺序

mdn文档上说它是在每一次重绘前执行的。

      const div = document.querySelector('div')
      div.addEventListener('click', function() {
        this.style.backgroundColor = 'black'
        requestAnimationFrame(() => {
          while (1) {
            console.log('requestAnimationFrame')
          }
        })
      })

结果是不变黑,也就是表明其确实是在重绘之前执行

requestAnimationFrame与promise的顺序

	  const div = document.querySelector('div')
      div.addEventListener('click', function() {
        requestAnimationFrame(() => {
          while (1) {
            console.log('requestAnimationFrame')
          }
        })
        Promise.resolve(1).then(() => {
          while (true) {
            console.log('promise')
          }
        })
      })

控制台一直打印promise,也就是说,滞后与promise。
所以我们就可以知道执行的优先度了

结论

	  const body = document.querySelector('body')
      const div = document.querySelector('div')
      body.addEventListener('click', function() {
        div.style.display = 'block'
        requestAnimationFrame(() => {
          div.style.height = '200px'
        })
      })

上面的代码是
同步任务>promise等微任务>制作render树(display = ‘block’)>requestAnimationFrame>制作render树(height = ‘200px’(对比前一颗render的相同元素产生动画))>第一帧重绘完成>setTimeout等宏任务通过这个优先度可以去理解很多玄学的东西。

拓展

比如说下面这个为什么不会产生动画。

	  const body = document.querySelector('body')
      const div = document.querySelector('div')
      body.addEventListener('click', function() {
        div.style.display = 'block'
        div.style.height = '200px'
      })

它的周期是
同步任务>promise等微任务>制作render树(display = ‘block’,height = ‘200px’)>第一帧重绘完成>setTimeout等宏任务
这里根本对比不了同一个元素节点的样式,因为display:none的时候元素根本不会被画进render树,在制作(display = ‘block’,height = ‘200px’)的dom树时,只会把这节点看成新增的,新增的默认不会加动画。
通过draf可以将阻塞的dom操作放入第二帧,然后在第一帧的时候开始将gpu放入动画。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值