微任务、宏任务和EventLoop(附带真题演练)

159 阅读3分钟

微任务、宏任务和EventLoop

拉到最低,省流总结

宏任务

浏览器Node
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame
  • setImmediate:该方法用来把一些需要长时间运行的操作放在一个回调函数里,在浏览器完成后面的其他语句后,就立刻执行这个回调函数。
  • requestAnimationFrame: 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行.[是宏任务的一个步骤,执行晚于微任务]

微任务

微任务浏览器Node
Promise.nextTick
MutationObserver
Promise.then catch finally

EventLoop

Js 是单线程执行,最终的目标是按照既定的规则将同步和异步的事件组织起来,使其按照规则顺序全部执行完毕。

执行的规则为:

  1. 执行栈(同步操作)为空时,检查微任务队列。
  2. 依次执行到微任务队列为空。检查宏任务队列。
  3. 执行宏任务队列头任务完毕,重复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执行的规则为:

  1. 执行栈(同步操作)为空时,检查微任务队列。
  2. 依次执行到微任务队列为空。检查宏任务队列。
  3. 执行宏任务队列头任务完毕,重复2,重复3直到宏任务队列为空。

I/O 交互带来的事件冒泡,都是在宏任务队列中执行

执行栈中的事件冒泡,都是直接在执行栈中执行

微任务队列中的observer 执行时会合并执行

requestAnimationFrame执行先于timeout

\