简单理解Vue中的nextTick

Vue 中的 nextTick 涉及到 Vue 中 DOM 的异步更新

为什么需要 nextTick

Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须 对数据更改–刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback) 这个 api 了

理解原理前的准备

首先需要知道事件循环中宏任务和微任务这两个概念

详细介绍指路 javascript 执行机制

常见的宏任务有:script,setTimeout,setInterval,setImmediate,I/O,UI rendering
常见的微任务有:process.nextTick(nodejs),Promise.then(),MutationObserver

理解 nextTick 的原理

正是 vue 通过异步队列控制 DOM 更新和 nextTick 回调函数先后执行的方式。如果大家看过这部分的源码,会发现其中做了很多 isNative()的判断,因为这里还存在兼容性优雅降级的问题

示例

先来一个示例了解下关于 Vue 中的 DOM 更新以及 nextTick 的作用。

模板

1
2
3
4
5
6
7
<div class="app">
<div ref="msgDiv">{{msg}}</div>
<div v-if="msg1">Message got outside $nextTick: {{msg1}}</div>
<div v-if="msg2">Message got inside $nextTick: {{msg2}}</div>
<div v-if="msg3">Message got outside $nextTick: {{msg3}}</div>
<button @click="changeMsg">Change the Message</button>
</div>

Vue 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Vue({
el: ".app",
data: {
msg: "Hello Vue.",
msg1: "",
msg2: "",
msg3: ""
},
methods: {
changeMsg() {
this.msg = "Hello world.";
this.msg1 = this.$refs.msgDiv.innerHTML;
this.$nextTick(() => {
this.msg2 = this.$refs.msgDiv.innerHTML;
});
this.msg3 = this.$refs.msgDiv.innerHTML;
}
}
});

点击前

点击后

从图中可以得知:msg1 和 msg3 显示的内容还是变换之前的,而 msg2 显示的内容是变换之后的。其根本原因是因为 Vue 中 DOM 更新是异步的

异步更新队列

Vue 在内部对异步队列尝试使用原生的Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

Promise.then 的延迟调用

1
2
3
4
5
6
7
8
9
10
11
12
if (typeof Promise !== "undefined" && isNative(Promise)) {
var p = Promise.resolve();
var logError = function (err) {
console.error(err);
};
timerFunc = function () {
p.then(nextTickHandler).catch(logError);
if (isIOS) {
setTimeout(noop);
}
};
}

如果浏览器支持 Promise,那么就用 Promise.then 的方式来延迟函数调用,Promise.then 方法可以将函数延迟到当前函数调用栈最末端,也就是函数调用栈最后调用该函数。从而做到延迟。

MutationObserver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
else if (
typeof MutationObserver !== "undefined" &&
(isNative(MutationObserver) ||
MutationObserver.toString() === "[object MutationObserverConstructor]")
) {
var counter = 1;
var observer = new MutationObserver(nextTickHandler);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = function () {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
}

MutationObserver 是 h5 新加的一个功能,其功能是监听 dom 节点的变动,在所有 dom 变动完成后,执行回调函数。

具体有一下几点变动的监听:

  • childList:子元素的变动
  • attributes:属性的变动
  • characterData:节点内容或节点文本的变动
  • subtree:所有下属节点(包括子节点和子节点的子节点)的变动

可以看出,以上代码是创建了一个文本节点,来改变文本节点的内容来触发的变动,因为我们在数据模型更新后,将会引起 dom 节点重新渲染,所以,我们加了这样一个变动监听,用一个文本节点的变动触发监听,等所有 dom 渲染完后,执行函数,达到我们延迟的效果。

setTimeOut 延迟器

1
2
3
4
5
else {
timerFunc = function () {
setTimeout(nextTickHandler, 0);
};
}

利用 setTimeout 的延迟原理,setTimeout(func, 0)会将 func 函数延迟到下一次函数调用栈的开始,也就是当前函数执行完毕后再执行该函数,因此完成了延迟功能。