为什么需要 nextTick
在同一段逻辑中,上半段我们更新了视图,下半段又依赖上半段更新完以后的新视图,同步执行上半段逻辑与下半段逻辑,那一定是不可行的,我们需要将下半段逻辑作为一个延时任务放到下一次事件循环中执行。读伪代码也许更有助理解:
function init() {
createMessageContainer() // 更新视图
message.open() // 使用新的视图。这里会报错,因为依赖上面更新完后得到的新视图
}
// 于是我们希望可以派发一个延时任务
function init() {
createMessageContainer() // 更新视图
nextTick(() => {
message.open() // 使用新的视图
})
}
我们希望 nextTick 可以接收一个回调函数,这个回调函数包裹了我们希望在这一次事件循环结束以后执行的逻辑。
那么如何实现这个 nextTick 就是我们讨论的重点。
如何实现 nextTick
nextTick 需要将传入的回调函数包装成异步任务,异步任务分为宏任务与微任务,为了尽快执行,所以应该优先选择微任务。但是我们也需要考虑兼容性,最后我们选用 API 的优先级如下:
- Promise
- MutationObserver
- setImmediate
- setTimeout
function nextTick(callback) {
let timerFunc;
if (Promise可用) {
// 使用 Promise
} else if (MutationObserver可用) {
// 使用 MutationObserver
} else if (setImmediate可用) {
// 使用 setImmediate
} else {
// 微任务都不被支持,使用 setTimeout
}
timerFunc(); // 派发延时任务
}
有了设计思路,实现起来易如反掌:
function nextTick(callback) {
let timerFunc;
if (typeof Promise !== 'undefined') {
const p = Promise.resolve();
timerFunc = () => {
p.then(callback);
};
} else if (
typeof MutationObserver !== 'undefined' &&
MutationObserver.toString() === '[object MutationObserverConstructor]'
) {
let counter = 1;
const observer = new MutationObserver(callback);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, { characterData: true });
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter); // 数据更新
};
} else if (typeof setImmediate !== 'undefined') {
timerFunc = () => {
setImmediate(callback);
};
} else {
timerFunc = () => {
setTimeout(callback, 0);
};
}
timerFunc();
}
总结
读到这里你应该已经发现以上就是 Vue 中 nextTick 的原理了~
异步任务的巧妙使用可以帮助我们更好的玩转 JavaScript~