chrome事件迴圈的自問自答

2023-07-21 12:01:04

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);

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內執行;

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>

該任務的優先順序比較低,多個平行宣告的requestIdleCallback會拆開成單一的task, 兩個連續task之間甚至會被內部的setTimeout插足。

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

6. 事件迴圈圖例