chrome事件循环的自问自答

  • chrome事件循环的自问自答已关闭评论
  • 107 次浏览
  • A+
所属分类:Web前端
摘要

在合适时机,这些宏任务会被推入宏任务队列;每一次事件循环会从宏任务队列中取一个任务执行。


chrome事件循环的自问自答

目录

1. 宏任务有哪些?

  • 事件回调 (js调用的 click box.click()
  • XHR或网络请求回调
  • 定时器的回调
  • I/O回调
  • history相关回调
  • MessageChannel的message回调

在合适时机,这些宏任务会被推入宏任务队列;每一次事件循环会从宏任务队列中取一个任务执行。

history.back回调:

<button id='box'>forward</button> <button id='box2'>back</button>  <script>   var box = document.getElementById('box');   var box2 = document.getElementById('box2');   box.addEventListener('click',()=>{     history.pushState('state',null,'?page=1');   })    window.addEventListener('popstate',function (ev) {     console.log('popstate');   })   box2.addEventListener('click',()=>{     history.back();     setTimeout(()=>{       console.log('timeout');     })   }) </script> 

MessageChannel的message回调

<button id='btn'>btn</button> <script> var btn = document.getElementById('btn'); btn.onclick = function () {   var channel = new MessageChannel();     channel.port1.onmessage = function onmessage1 (){       console.log('postMessage')       Promise.resolve().then(function promise1 (){           console.log('promise')       })     };     setTimeout(function setTimeout2(){       console.log('setTimeout')     }, 0)     channel.port2.postMessage(0); }; </script> 

I/O回调

<input type="file" id="input" multiple> <script>   input.addEventListener('change', function () {     var file = input.files[0]     var reader = new FileReader()     reader.onload = function (ev) {       console.log(reader.result);     }     reader.readAsArrayBuffer(file)   }) </script> 

2. 微任务有哪些?

MutationObserver的回调、Promise的then catch finally回调、queueMicrotask.

在合适时机,这些微任务会被推入微任务队列;每一次事件循环会从微任务队列中取所有任务并执行。

Promise和queueMicrotask不支持IE, MutationObserver支持IE11;

MutationObserver例子:

下面的代码中box.textContent = 1的位置不同,代码的执行顺序就不同以验证MutationObserver为微任务。

<div id='box'>0</div> <script>   const box = document.getElementById('box');   const mo = new MutationObserver(function (mutations) {     console.log('mutations')   })   mo.observe(box, {     childList: true   })   box.onclick = function () {     // box.textContent = 1;     Promise.resolve().then(()=>{       console.log(333)     })     box.textContent = 1;   }; </script> 

以下是使用`queueMicrotask`方法手动添加微任务的例子,可以不会对更高优先级的代码运行造成干扰。

<div id='box'>0</div> <script>   const box = document.getElementById('box');   box.onclick = function () {     queueMicrotask(()=>{       console.log(121212)     })     console.log(333)   }; </script> 

3. dom渲染是事件循环的一部分么?

从规范的角度来看,DOM渲染是事件循环的一部分,可以将其视为一种渲染任务。

如果宏任务或者微任务中发生了dom修改,因为一个渲染帧的时间可能远大于事件循环周期,所以不一定在本次事件循环会执行渲染任务。

<div id='box'>0</div> <script>   const box = document.getElementById('box');   box.onclick = function () {     setTimeout(function setTimeout17 () {       box.textContent = 1;     }, 0)     setTimeout(function setTimeout18 () {       box.textContent = 2;     }, 0)   }; </script> 

下图是上面的代码的执行流程,两个setTimeout的回调执行代表两次事件循环,在其后面出现了一个新的Task,仅执行了一次布局(layout)和绘制(paint);

chrome事件循环的自问自答

4. requestAnimationFrame的回调是宏任务还是微任务?

requestAnimationFrame的回调函数会在浏览器在下一帧渲染之前执行, 既不是宏任务,也不是微任务, 从规范上看是事件循环的一部分,从下图可以看到一个task下包含了requestAnimationFrame,layout paint, 可将其归类于渲染任务的一个可选步骤。

<div id='box'>0</div> <script>   const box = document.getElementById('box');   box.onclick = function () {     setTimeout(function setTimeout17 () {       box.textContent = 1;       requestAnimationFrame(()=>{         console.log(111)          Promise.resolve().then(()=>{           console.log(333)         })       })     }, 0)     setTimeout(function setTimeout18 () {       box.textContent = 2;       requestAnimationFrame(()=>{         console.log(222)       })     }, 0)   }; </script> 

上述代码中添加了两个requestAnimationFrame,可以看到两者在一个Task内顺序执行;并且回调中的微任务也在这个Task内执行;

chrome事件循环的自问自答

5. requestIdleCallback的回调是宏任务还是微任务?

requestIdleCallback是事件循环的一部分,从图中可以看到,requestIdleCallback的回调是一个特殊的任务,这个函数的回调会在浏览器空闲时期被调用,所以不是每次循环都会执行该任务。

<button id='btn'>btn</button> <script> var btn = document.getElementById('btn'); btn.onclick = function () {   requestIdleCallback(function () {     btn.innerHTML = "sdfsdfs"     setTimeout(()=>{       console.log(3)     },0)     Promise.resolve().then(()=>{       console.log(4)     })   }) }; </script> 

chrome事件循环的自问自答

该任务的优先级比较低,多个平行声明的requestIdleCallback会拆开成单一的task, 两个连续task之间甚至会被内部的setTimeout插足。

for (let i = 0; i < 10; i++) {   requestIdleCallback(() => {     console.log('idle', Date.now() - a)     setTimeout(()=>{       console.log(12121)     })   }) } 

chrome事件循环的自问自答

6. 事件循环图例

chrome事件循环的自问自答