打開前端項目中的 package.json,會發現眾多工具已經占據了我們開發日常的各個角落,它們的存在於我們的開發而言是不可或缺的。有沒有想過這些工具的功能是如何實現的呢?沒錯,抽象語法樹 (Abstract Syntax Tree) 就是上述工具的基石。 ...
chrome事件迴圈的自問自答
目錄
- 1. 巨集任務有哪些?
- 2. 微任務有哪些?
- 3. dom渲染是事件迴圈的一部分麽?
- 4. requestAnimationFrame的回調是巨集任務還是微任務?
- 5. requestIdleCallback的回調是巨集任務還是微任務?
- 6. 事件迴圈圖例
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)
})
})
}