# 必知的语法
1. [MutationObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)
2. [setImmediate](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setImmediate)
# 源码
```js
export let isUsingMicroTask = false
const callbacks = []
let pending = false
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
/*
这里我们使用微任务使用异步延迟包装器。
在2.5中,我们使用了(宏)任务(与微任务结合使用)。但是,当状态在重新绘制之前被更改时,它会有一些微妙的问题 (例如#6813,out-in transitions)。
此外,在事件处理程序中使用(宏)任务会导致一些奇怪的行为,这是无法规避的
(例如#7109、#7153、#7546、#7834、#8109)。
所以我们现在到处都在使用微任务。
这种权衡的一个主要缺点是存在一些场景:
微任务的优先级过高,并在支持的顺序事件两者之间触发(例如#4521、#6690,它们有解决方案)
或者甚至是在同一事件(#6566)之间冒泡。
*/
let timerFunc
/*
nextTick行为利用了微任务队列,可以通过Promise.then或MutationObserver访问该队列。
MutationObserver获得了更广泛的支持,但是它受到了严重的干扰,此干扰是在ios> = 9.3.3中的UIWebView触发触摸事件处理程序。触发几次后完全停止工作…因此,如果native Promise可用,我们将使用它:
*/
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
/*
在有问题的UIWebViews中,Promise.then不会完全中断,
但它会陷入一种奇怪的状态,即回调被推入微任务队列,但队列没有被刷新,直到浏览器需要做一些其他的工作,比如处理一个计时器。
因此,我们可以通过添加一个空计时器来“强制”刷新微任务队列。
*/
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
/*
在native Promise不可用时使用MutationObserver,
例如PhantomJS, iOS7, android4.4 (#6466 MutationObserver在IE11中是不可靠的)
MutationObserver是用来监听目标DOM结构是否改变
*/
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
// 文本节点的值更改时是否调用观察者的回调函数
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
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)
}
}
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)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
```
# nextTick 原理
主要通过事件循环(Event Loop),Vue在更新Dom时是异步执行,只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一时间循环中发生的所有数据变更.如果同一个watcher被触发多次,只会被推入到队列中一次.这种在缓冲中去掉重复数据对于避免不必要的计算和Dom操作是非常重要的.Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
# 链接
[面试题:Vue中$nextTick原理](https://www.pianshen.com/article/23901398287/)
Vue.nextTick源码分析