在实现 Promise/A+ 库的过程当中,第一次据说了 JavaScript 中的 macrotask 和 microtask 的概念。而后 Google 搜索到了如下的资料:html
阅读以后结合我本身的理解,来讲说这二者的区别。node
在那以前,咱们先说一说 JavaScript 中的事件循环机制,上面两个连接中的文章和视频很是详细的解释了该机制。咱们只简单的解释一下,先运行下面的一段代码:web
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); console.log('script end');
这里一看,setTimeout
的延时为 0 ,那么是否是程序执行到这里以后就当即执行setTimeout
里面的函数呢?其实不是的,此段代码最后的输出结果为:api
"script start" "script end" "setTimeout"
这是由于 JavaScript 主线程拥有一个 执行栈 以及一个 任务队列,主线程会依次执行代码,当遇到函数时,会先将函数 入栈,函数运行完毕后再将该函数 出栈,直到全部代码执行完毕。promise
那么遇到 WebAPI(例如:setTimeout
, AJAX
)这些函数时,这些函数会当即返回一个值,从而让主线程不会在此处阻塞。而真正的异步操做会由浏览器执行,浏览器会在这些任务完成后,将事先定义的回调函数推入主线程的 任务队列 中。浏览器
而主线程则会在 清空当前执行栈后,按照先入先出的顺序读取任务队列里面的任务。app
那么咱们来看一下上面程序的执行顺序:webapp
// 1. 开始执行 console.log('script start'); // 2. 打印字符串 "script start" setTimeout( function() { // 5. 浏览器在 0ms 以后将该函数推入任务队列 // 而到第5步时才会被主线程执行 console.log('setTimeout'); // 6. 打印字符串 "setTimeout" }, 0 ); // 3. 调用 setTimeout 函数,并定义其完成后执行的回调函数 console.log('script end'); // 4. 打印字符串 "script end" // 5. 主线程执行栈清空,开始读取 任务队列 中的任务
以上就是浏览器的异步任务的执行机制,核心点为:
AJAX
请求,仍是setTimeout
等 API,浏览器内核会在其它线程中执行这些操做,当操做完成后,将操做结果以及事先定义的回调函数放入 JavaScript 主线程的任务队列中Macrotask 和 microtask 都是属于上述的异步任务中的一种,咱们先看一下他们分别是哪些 API :
setTimeout
, setInterval
, setImmediate
, I/O, UI renderingmicrotasks: process.nextTick
, Promises
, Object.observe
(废弃), MutationObserver
setTimeout
的 macrotask ,和 Promise
的 microtask 有什么不一样呢? 咱们经过下面的代码来展示他们的不一样点:
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');
(代码来自 Tasks, microtasks, queues and schedules,原文有执行顺序的可视化操做演示,推荐观看)
在这里,setTimeout
的延时为0,而Promise.resolve()
也是返回一个被resolve
了promise
对象,即这里的then
方法中的函数也是至关于异步的当即执行任务,那么他们究竟是谁在前谁在后?
咱们看看最终的运行结果(node 7.7.3):
"script start" "script end" "promise1" "promise2" "setTimeout"
这里的运行结果是Promise
的当即返回的异步任务会优先于setTimeout
延时为0的任务执行。
缘由是任务队列分为 macrotasks 和 microtasks,而Promise
中的then
方法的函数会被推入 microtasks 队列,而setTimeout
的任务会被推入 macrotasks 队列。在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microtasks 队列清空。
注:通常状况下,macrotask queues 咱们会直接称为 task queues,只有 microtask queues 才会特别指明。
那么也就是说若是个人某个 microtask 任务又推入了一个任务进入 microtasks 队列,那么在主线程完成该任务以后,仍然会继续运行 microtasks 任务直到任务队列耗尽。
而事件循环每次只会入栈一个 macrotask ,主线程执行完该任务后又会先检查 microtasks 队列并完成里面的全部任务后再执行 macrotask
如今咱们根据 HTML Standard - event loop processing model来描述浏览器的事件循环的进程模型:
perform a microtask checkpoint 的执行步骤:
在咱们的浏览器环境的事件循环中, JavaScript 脚本也会做为一个 task 被推入 task queue,咱们在运行这个事件后,该脚本中的 microtasks,tasks 才会被推入队列。
Vue 中如何使用 MutationObserver 作批量处理 - 顾轶灵的回答
为啥要用 microtask?根据 HTML Standard,在每一个 task 运行完之后,UI 都会重渲染,那么在 microtask 中就完成数据更新,当前 task 结束就能够获得最新的 UI 了。反之若是新建一个 task 来作数据更新,那么渲染就会进行两次。
根据咱们上面提到的事件循环进程模型,每一次执行 task 后,而后执行 microtasks queue,最后进行页面更新。若是咱们使用 task 来设置 DOM 更新,那么效率会更低。而 microtask 则会在页面更新以前完成数据更新,会获得更高的效率。
immediate 库是一个跨浏览器的 microtask 实现。
这个库使用原生 JavaScript 实现了可以兼容 IE6 的 microtask ,若是对实现机制比较感兴趣的能够去阅读这个库的源码,我后面会写一篇文章来详细的介绍一下其实现。
原文连接:https://juejin.im/entry/58d4df3b5c497d0057eb99ff