简要总结microtask和macrotask

前言

我是在作前端面试题中看到了setTimeout和Promise的比较,而后第一次看到了microtask和macrotask的概念,在阅读了一些文章以后发现没有一个比较全面易懂的文章,因此我尝试作一个梳理性的总结.javascript

这道经典的面试题引发了个人兴趣

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

JavaScript的事件循环机制

首先咱们先弄清楚setTimeout和Promise的共同点,也就是我第一次的看到那道面试题的疑惑点.html

JavaScript 主线程拥有一个 执行栈 以及一个 任务队列,主线程会依次执行代码,当遇到函数时,会先将函数 入栈,函数运行完毕后再将该函数 出栈,直到全部代码执行完毕。前端

上面的例子的执行栈执行顺序应该是这样的java

console.log('script start');
console.log('script end');
Promise.resolve();

而任务队列的执行顺序应该是这样的git

Promise.then(function() {
  console.log('promise1');
});
Promise.then(function() {
  console.log('promise2');
});
setTimeout(function() {
  console.log('setTimeout');
}, 0);

而主线程则会在 清空当前执行栈后,按照先入先出的顺序读取任务队列里面的任务。github

众所周知setTimeout和Promise.then()都属于上述异步任务的一种,那到底为何setTimeout和Promise.then()会有顺序之分,这就是我想分析总结的问题所在了.web

macrotasks(tasks) 和 microtasks

tasks

tasks的做用是为了让浏览器可以从内部获取javascript / dom的内容并确保执行栈可以顺序进行。面试

tasks的调度是随处可见的,例如解析HTML,得到鼠标点击的事件回调等等,在这个例子中,咱们所迷惑的setTimeout也是一个tasks.api

microtasks

microtasks一般用于在当前正在执行的脚本以后直接发生的事情,好比对一系列的行为作出反应,或者作出一些异步的任务,而不须要新建一个全新的tasks。promise

只要执行栈没有其余javascript在执行,在每一个tasks结束时,microtasks队列就会在回调后处理。在microtasks期间排队的任何其余microtasks将被添加到这个队列的末尾并进行处理。

microtasks包括mutation observer callbacks,就像上例中的promise callbacks同样。

因此上面的例子执行顺序的实质是

  • tasks =>start end以及resolve
  • microtasks =>promise1和promise2
  • tasks =>setTimeout

具体应用

须要注意的是,在两个tasks之间,浏览器会从新渲染。这也是咱们须要了解tasks和microtasks的一个很是重要的缘由.

Vue 中如何使用 MutationObserver 作批量处理? - 顾轶灵的回答 - 知乎

根据 HTML Standard,在每一个 task 运行完之后,UI 都会重渲染,那么在 microtask 中就完成数据更新,当前 task 结束就能够获得最新的 UI 了。反之若是新建一个 task 来作数据更新,那么渲染就会进行两次。

浏览器兼容问题

在__Microsoft Edge__, Firefox 40__, __iOS Safari 以及 desktop Safari 8.0.8 中setTimeout会先于Promise

该例子来自Jake Archibald-->Tasks, microtasks, queues and schedules,其中有动画来展示tasks和microtasks的具体工做流程,十分推荐阅读

//html
<div class="outer">
  <div class="inner"></div>
</div>
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

// Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

// Here's a click listener…
function onClick() {
  console.log('click');

  setTimeout(function() {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function() {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

在这个例子中,不一样浏览器的log是不一样的,以下所示

Chrome Firefox Safari edge
click click click click
promise mutate mutate click
mutate click click mutate
click mutate mutate timeout
promise timeout promise promise
mutate promise promise timeout
timeout promise timeout promise
timeout timeout timeout \

事实上Chrome是正确的,并且由此可发现microtasks并非在tasks的结束阶段开始执行,而是在tasks中回调结束以后(只要没有正在执行的JavaScript代码)

总结

tasks会顺序执行,浏览器会在执行间隔从新渲染

microtasks会顺序执行,执行时机为

在没有JavaScript代码执行的callback以后

在每个tasks以后


因为我是前端初学者,对于JavaScript还很不熟悉,对事件循环的进程模型不是很了解,但愿这篇文章可以帮助你们.

事件循环机制建议参考文章

阮一峰-->JavaScript 运行机制详解:再谈Event Loop

HTML Living Standard — Last Updated 9 April 2018

tasks建议参考文章

Jake Archibald-->Tasks, microtasks, queues and schedules

理解 JavaScript 中的 macrotask 和 microtask

setImmediate.js --A YuzuJS production

相关文章
相关标签/搜索