可以通过 vue 的源码 nextTick 方法:里面描述了浏览器中的常见的宏任务和微任务
- 宏任务:script、ui、setTimeout、setInterval、requestFrameAnimation、setImmediate(ie 10以上、node 中支持)、MessageChannel(消息通道)都是异步的,还有 click,ajax
- 微任务:语言本身提供的 promise.then、mutationObserver、nextTick
【nextTick 源码:我这里用 2.6.11 版本的】https://github.com/vuejs/vue/blob/v2.6.11/src/core/util/next-tick.js
Vue 在内部对异步队列判断当前环境优先支持的异步方法,优先选择微任务,其顺序如下:
- 先判断当前环境是否原生支持 promise
- 如果不支持 promise, 就判断是否支持 MutationObserver,不是IE环境,并且原生支持 MutationObserver
- 判断当前环境是否原生支持 setImmediate
- 以上三种都不支持就选择 setTimeout(setTimeout 可能产生一个 4ms 的延迟,而 setImmediate 会在主线程执行完后立刻执行)
优先级: Promise —> MutationObserver —> setImmediate—> setTimeout
/* @flow */ /* globals MutationObserver */ // noop 表示一个无操作空函数,用作函数默认值,防止传入 undefined 导致报错 import { noop } from "shared/util"; // 错误处理函数 import { handleError } from "./error"; // 环境判断函数,isNative 判断某个属性或方法是否原生支持,如果不支持或通过第三方实现支持都会返回false import { isIE, isIOS, isNative } from "./env"; // 标记 nextTick 最终是否以微任务执行 export let isUsingMicroTask = false; // 存放调用 nextTick 时传入的回调函数 const callbacks = []; // 标记是否已经向任务队列中添加了一个任务,如果已经添加了就不能再添加了 // 当向任务队列中添加了任务时,将 pending 置为true, 当任务被执行时将 pending 置为 false let pending = false; // flushCallbacks 函数用来遍历 callbacks 数组的拷贝并执行其中的回调 function flushCallbacks() { pending = false; // 拷贝一份 callbacks const copies = callbacks.slice(0); // 然后清空 callbacks callbacks.length = 0; // 遍历执行传入的回调 for (let i = 0; i < copies.length; i++) { copies[i](); } } // Here we have async deferring wrappers using microtasks. // In 2.5 we used (macro) tasks (in combination with microtasks). // However, it has subtle problems when state is changed right before repaint // (e.g. #6813, out-in transitions). // Also, using (macro) tasks in event handler would cause some weird behaviors // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). // So we now use microtasks everywhere, again. // A major drawback of this tradeoff is that there are some scenarios // where microtasks have too high a priority and fire in between supposedly // sequential events (e.g. #4521, #6690, which have workarounds) // or even between bubbling of the same event (#6566). let timerFunc; // The nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore next, $flow-disable-line */ if (typeof Promise !== "undefined" && isNative(Promise)) { const p = Promise.resolve(); timerFunc = () => { // 用 promise.then 把 flushCallbacks 函数包裹成个异步微任务 p.then(flushCallbacks); // In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. // 因为在 ios 下 promise.then 后面没有宏任务的话,微任务队列不会刷新,这里的 setTimeout 是用来强制刷新微任务队列 if (isIOS) setTimeout(noop); }; // 标记当前 nextTick 使用的微任务 isUsingMicroTask = true; } else if ( !isIE && typeof MutationObserver !== "undefined" && (isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === "[object MutationObserverConstructor]") ) { // Use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) let counter = 1; // new 一个 MutationObserver 类 const observer = new MutationObserver(flushCallbacks); // 创建一个文本节点 const textNode = document.createTextNode(String(counter)); // 监听这个文本节点,当数据发生变化就执行 flushCallbacks observer.observe(textNode, { characterData: true }); timerFunc = () => { counter = (counter + 1) % 2; // 更新数据 textNode.data = String(counter); }; // 标记当前 nextTick 使用的微任务 isUsingMicroTask = true; } else if (typeof setImmediate !== "undefined" && isNative(setImmediate)) { // Fallback to setImmediate. // Technically it leverages the (macro) task queue, // but it is still a better choice than setTimeout. timerFunc = () => { setImmediate(flushCallbacks); }; } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0); }; } // 声明 nextTick 函数,接收一个回调函数和一个执行上下文作为参数 export function nextTick(cb?: Function, ctx?: Object) { let _resolve; // 将传入的回调函数存放到数组中,后面会遍历执行其中的回调 callbacks.push(() => { if (cb) { try { cb.call(ctx); } catch (e) { handleError(e, ctx, "nextTick"); } } else if (_resolve) { _resolve(ctx); } }); // 当在同一轮事件循环中多次调用 nextTick 时,timerFunc 只会执行一次 if (!pending) { pending = true; timerFunc(); } // 如果没有传入回调,并且当前环境支持 Promise,就返回一个 Promise,在返回的这个 promise.then 中 DOM 已经更新好了 // $flow-disable-line if (!cb && typeof Promise !== "undefined") { return new Promise((resolve) => { _resolve = resolve; }); } }