微任务、宏任务和EventLoop
拉到最低,省流总结
宏任务
| 宏 | 浏览器 | Node |
|---|---|---|
| I/O | ✅ | ✅ |
| setTimeout | ✅ | ✅ |
| setInterval | ✅ | ✅ |
| setImmediate | ❌ | ✅ |
| requestAnimationFrame | ✅ | ❌ |
- setImmediate:该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。
- requestAnimationFrame: 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行.[是宏任务的一个步骤,执行晚于微任务]
微任务
| 微任务 | 浏览器 | Node |
|---|---|---|
| Promise.nextTick | ❌ | ✅ |
| MutationObserver | ✅ | ❌ |
| Promise.then catch finally | ✅ | ✅ |
EventLoop
Js 是单线程执行,最终的目标是按照既定的规则将同步和异步的事件组织起来,使其按照规则顺序全部执行完毕。
执行的规则为:
- 执行栈(同步操作)为空时,检查微任务队列。
- 依次执行到微任务队列为空。检查宏任务队列。
- 执行宏任务队列头任务完毕,重复2,重复3直到宏任务队列为空。
真题
#HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="outer">
outer
<div class="inner">inner</div>
</div>
</body>
</html>
# css
.outer {
background-color: red;
height: 200px;
width: 200px;
}
.inner {
background-color: blue;
height: 100px;
width: 100px;
}
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
function onClick() {
console.log('click');
setTimeout(function() {
console.log('timeout');
}, 0);
requestAnimationFrame(_ => console.log('animationFrame'))
Promise.resolve().then(function() {
console.log('promise');
});
outer.setAttribute('data-random', Math.random())
}
new MutationObserver(() => {
console.log('observer')
}).observe(outer, {
attributes: true
})
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
//inner.click()
Q1: 此时鼠标点击inner,页面的输出是?
(1) 点击inner,是为一次I/O 操作,宏任务队列注入“onClick(by inner)”,因为事件冒泡,宏任务队列注入“onClick(by outer)”.当前执行栈为空,检查微任务队列为空,检查宏任务队列有值,取值执行
(2) 执行“onClick(by inner)”,执行栈压入“click”。向微任务队列压入"promise""observer".向宏任务队列注入"timeout"animationFrame"。此时先j检查执行栈,输出“click” 。检查微任务队列不空,依次输出"promise""observer". 检查宏任务队列["onClick(by outer)","timeout(by inner)","animationFrame(by inner)"]不为空。
(3) 取宏队列顶"onClick(by outer)" 运行。执行栈压入“click”,向微任务队列压入"promise""observer".向宏任务队列注入"timeout""animationFrame"。此时先j检查执行栈,输出“click” 。检查微任务队列不空,依次输出"promise""observer". 检查宏任务队列["timeout(by inner)","animationFrame(by inner)","timeout(by outer)","animationFrame(by outer)"]
(4) 由于requestAnimationFrame 是在帧与帧之间的间歇执行,所以优先于timeout输出。依次输出"animationFrame" "animationFrame" "timeout" "timeout"
"click"
"promise"
"observer"
"click"
"promise"
"observer"
"animationFrame"
"animationFrame"
"timeout"
"timeout"
Q2: 使用 inner.click(),页面的输出是?
(1) 页面显式调用inner.click()。 从上到下执行。 执行栈压入“inner.click()”, 事件冒泡发生,执行栈再次压入“outer.click()”,微任务队列为空,宏任务队列为空。
(2) 执行栈取“inner.click()”,执行,输出“click” 。向微任务队列压入"promise""observer".向宏任务队列注入"timeout""animationFrame"。检查执行栈不空,取"outer.click()"执行,输出“click” ,向微任务队列压入"promise""observer".向宏任务队列注入"timeout""animationFrame"。
此时的执行栈为空。
(3) 检查微任务队列【"promise","observer","promise","observer"】.由于observer 会合并执行(即连续触发多次也只执行一次)。输出"promise" "observer" "promise" 。微任务队列为空。检查宏任务队列。
(4) 由于requestAnimationFrame 是在帧与帧之间的间歇执行,所以优先于timeout输出。依次输出"animationFrame" "animationFrame" "timeout" "timeout"
"click"
"click"
"promise"
"observer"
"promise"
"animationFrame"
"animationFrame"
"timeout"
"timeout"
总结
EventLoop执行的规则为:
- 执行栈(同步操作)为空时,检查微任务队列。
- 依次执行到微任务队列为空。检查宏任务队列。
- 执行宏任务队列头任务完毕,重复2,重复3直到宏任务队列为空。
I/O 交互带来的事件冒泡,都是在宏任务队列中执行
执行栈中的事件冒泡,都是直接在执行栈中执行
微任务队列中的observer 执行时会合并执行
requestAnimationFrame执行先于timeout
\