事件循环

主线程等待和处理事件(消息)的编程设计模式。

主线程

这里主要做的事情是JS执行、DOM渲染。这意味着网页发生的事情是按照某个顺序执行的,不会发生竞争。
关键点GUI渲染线程与JS引擎线程是互斥的。因此当JS线程某个事件导致发生死循环时,会导致渲染阻塞,其他的事件也会阻塞。
关键点其他的线程会通过事件,触发主线程的回调函数。

事件环

关键点渲染可能发生在两个任务之间,也可能两次渲染之间有在多个任务
image.png

  • JS线程先执行结束,才会发生渲染。如果发生DOM变化,渲染线程才会接下来执行。因此下面代码不会发生闪烁。

    1. document.body.appendChild(el);
    2. el.style.display = 'none';
  • 下面的代码不会发生渲染阻塞,因为JS代码有执行结束,只不过计时器函数放到任务队列等待执行。

    1. function loop(){
    2. setTimeout(loop, 100);
    3. }
    4. loop();

    requestAnimationFrame(rAF回调队列)

    关键点会在下一帧的开始执行,直到队列为空,然后执行渲染。

    1. function callback(){
    2. moveBoxOnePixel(); //盒子移动1px
    3. setTiomeout(callback, 0); //因为显示器的刷新频率是60HZ,因此如果使用计时器不断的渲染页面,其实是无意义的损耗性能。渲染数据准备好了但是显示屏不会立即刷新,用户也看不到。
    4. requestAnimationFrame(callback)
    5. }
    6. callback();

    image.png
    当你准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。
    image.png
    使用setTimeout渲染动画的缺点。每一帧动画的渲染时间无法确定,因为当有其他的事件需要执行时,导致动画推迟渲染。
    image.png
    关键点由浏览器决定什么时候渲染动画,而不是在JS代码执行一结束,就执行渲染。不会因任务的出现,影响下个时间段的页面渲染。
    image.png

    宏任务

    ```javascript //DOMNodeInserted事件监听DOM的变化。 document.body.addEventListener(‘DOMNodeInserted’, () => { console.log(1); })

for(var i = 0; i < 5; i++ ){ const div = document.createElement(‘div’); document.body.appendChild(div); } ``` 在事件循环中,一旦监听到事件发生,就会加入到队列中执行,每次循环执行一次,同时会发生一次渲染。对于批量的DOM操作,这是明显消耗性能的。我们希望浏览器多个事件触发后,等一会儿再执行渲染。
关键点宏任务执行一次,然后渲染一次,不断循环。

微任务

UI渲染需要等到全部微任务都执行完,再执行渲染。有点类似与文档片段,检测到全部的DOM变化之后,在执行渲染。
关键点在一次事件循环中微任务会一直执行,直到队列为空,然后再执行渲染。

参考

https://www.bilibili.com/video/BV1K4411D7Jb?from=search&seid=16036267928700117692&spm_id_from=333.337.0.0