对于Vue2中的nextTick,大家也都不陌生,**nextTick是将注入的回调函数以微任务的形式放到本次事件循环队列上去,如果不支持就放到下一次时间队列中**。接下来我们看一下nextTick的源码解析:
创建flushCallbacks函数执行器
const callbacks = [] // 用于存储$nextTick传入的函数
let pending = false
//flushCallbacks用于遍历执行回调函数
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
创建微任务包装器,做判断
let timerFunc;//使用微任务的异步延迟包装器
1、先判断浏览器是否支持Promise,如果为true,则用Promise来实现
if (typeof Promise !== 'undefined' && isNative(Promise)) {
//Promise.resolve()等价于 new Promise(resolve => resolve())
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks) //then函数注册回调
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
}
2、在判断浏览器是否支持MutationObserver,如果支持,则使用MutationObserver来实现
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
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
}
MutationObserver API在MDN描述是:提供了监视对DOM树所做更改的能力,它会在指定的DOM发生变化时被调用。vue是创建一个文本节点,通过监听文本节点变化来执行MutationObserver的注册函数调用
3、如果浏览器对上述微任务API都不支持,则使用setImmediate/setTimeout来异步执行回调
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)
}
}
4。判断出浏览器支持的API后,最后一步便是执行
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方法,可接受两个参数cb是回调函数,ctx执行上下文首先判断cb是否存在,存在则将绑定了ctx的回调函数push入callbacks中pending默认值false,然后调用timerFunc微任务包装器来执行我们传入的回调
** 总结:在 nextTick 函数中把通过参数 cb 传入的函数,做一下包装然后 push 到 callbacks 数组中。然后用变量 pending 来保证执行一个事件循环中只执行一次 timerFunc()。最后执行 if (!cb && typeof Promise !== 'undefined'),判断参数 cb 不存在且浏览器支持 Promise,则返回一个 Promise 类实例化对象。例如 nextTick().then(() => {}),当 _resolve 函数执行,就会执行 then 的逻辑中。