介绍NextTick 以及 应用场景
nextTick 是全局vue的一个函数,在vue系统中,用于处理 dom更新 的操作。 在vue生命周期的 created() 钩子函数进行的DOM操作要放在 Vue.nextTick() 的回调函数中,因为created()钩子函数执行的时候DOM并未进行任何渲染,所以此处一定要将DOM操作的JS代码放进Vue.nextTick()的回调函数中。而vue生命周期的 mounted() 钩子函数执行时,所有的DOM挂载和渲染都已完成,此时该钩子函数进行任何DOM操作都不会有问题。
NextTick方法的运行原理
- vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
- microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
- 因为浏览器和移动端兼容问题,vue不得不做了microtask向macrotask的兼容(降级)方案
NextTick 代码解析
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)
}
})
// pending = false 执行callbacks里的函数
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
nextTick接收一个函数参数,并且将这个函数放入了callbacks,之后调用了 timerFunc(),前者是一个空数组,后者其实就是一个微任务。
callbacks 和 flushCallbacks
// 回调函数队列
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]()
}
}
callbacks模拟回调函数队列
timerFunc
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
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)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
可以看出这边代码其实是做了四个判断,对当前环境进行不断的降级处理,尝试使用原生的Promise.then、MutationObserver和setImmediate,上述三个都不支持最后使用setTimeout;降级处理的目的都是将flushCallbacks函数放入微任务(判断1和判断2)或者宏任务(判断3和判断4),等待下一次事件循环时来执行。MutationObserver是Html5的一个新特性,用来监听目标DOM结构是否改变,也就是代码中新建的textNode;如果改变了就执行MutationObserver构造函数中的回调函数,不过是它是在微任务中执行的。
因此nextTick其实就是将回调函数放入在当前EventLoop的微任务队列。
总结
整体nextTick的代码都分析完毕了,总结一下它的整个流程就是
- 把回调函数放入callbacks等待执行
- 将执行函数放到微任务或者宏任务中
- 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调
这就是整个nextTick的执行逻辑