Vue
的 DOM
更新是异步的,Vue
的 nextTick
方法会在 DOM
更新完成后执行,从而避免后面的代码获取到错误的 DOM
内容。
<template>
<span ref="msg">{{message}}</span><button @click="testNextTick">修改</button>
<template>
<script>
export default {
data() {
return {
val: 1,
message: 'notchanged'
};
},
methods: {
testNextTick() {
// 改变数据
this.message = "changed";
// 这样不行,此时DOM还没有更新
console.log(this.$refs.msg.textContent); // 'notchanged'
// 这样可以,nextTick里面的代码会在DOM更新后执行
this.$nextTick(() => {
console.log(this.$refs.msg.textContent); //'changed'
});
}
}
}
</script>
原理:
1、JS
是单线程的,所有同步任务都在主线程上执行,形成一个执行栈(ECS)。
2、异步操作需要排队执行,在主线程之外有一个任务队列(Task Queue),只要异步操作有了结果,就在任务队列中放置一个事件。
3、异步操作有 setTimeout/setInterval
、Promise
等。
一旦执行栈中的所有同步任务执行完毕,引擎就会处理任务队列中的内容,依次执行那些事件所对应的回调函数。
Vue
在更新 DOM
时是异步执行的。只要侦听到数据变化,Vue
将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher
被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM
操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue
刷新队列并执行实际 (已去重的) 工作。Vue
在内部对异步队列尝试使用原生的 Promise.then
、MutationObserver
和 setImmediate
,如果执行环境不支持,则会采用 setTimeout(fn, 0)
代替。
例如,当你设置 vm.someData = 'new value'
,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM
状态来做点什么,这就可能会有些棘手。虽然 Vue.js
通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM
,但是有时我们必须要这么做。为了在数据变化之后等待 Vue
完成更新 DOM
,可以在数据变化之后立即使用 Vue.nextTick(callback)
。这样回调函数将在 DOM
更新完成后被调用。