Vue的DOM异步更新
数据驱动
数据驱动是 Vue 的核心思想,在 Vue 中我们只需要通过操作数据来操作 DOM(视图),什么意思呢?例如我们需要更改一个文本。
在原生 JS 中
html
<p id="title">你好</p>
<script>
let p1 = document.getElementById('title')
p1.innerText = 'hello'
</script>
在 Vue 中
html
<p id="app">{{text}}</p>
<script>
let vm = new App({
el: '#app',
data: {
text: '你好',
},
})
vm.text = 'hello'
</script>
在原生 JS 中我们需要先获取到 DOM,然后对 DOM 进行操作从而改变文本,但是在 Vue 中,我们只需要对数据进行修改就能更改对应的 DOM,我们并不需要直接操作 DOM 就能够实现对视图的修改。这就是 Vue 最核心的思想,视图(DOM)和数据是绑定的,通过操作数据就能够操作 DOM。
虚拟 DOM
这是因为在 Vue 中,DOM 和数据之间有映射关系,而这映射就是通过虚拟 DOM 来实现的,可以将虚拟 DOM 理解成一个真实 DOM 的描述对象,即虚拟 DOM 就是个对象,用于表示一个真实 DOM 结构的数据。 Vue 会将模板编译然后创建虚拟 DOM,这样我们通过操作数据(虚拟 DOM)就能够完成对真实 DOM 的修改。那虚拟 DOM 只是个 JS 数据,需要通过 Vue 把这个虚拟 DOM 渲染成一个真实的 DOM。
下图就是 Vue 创建的虚拟 DOM,也就是用一个对象来表示一个 DOM 结构

render 函数
render 函数是用于将数据生成对应的虚拟 DOM 的。 当我们修改数据后,Vue 会重新生成虚拟 DOM,然后完成 DOM 的更新,我们可以通过 render 函数去验证这点。 render 函数用于创建虚拟 DOM 的,当我们修改数据后这个 render 函数会重新执行一次,也就是会再次生产一个虚拟 DOM。
html
<div id="app" ref="root"></div>
<script>
var app = new Vue({
el: '#app',
data: {
text: 'hello',
},
render: function (h) {
var vdom = h('div', { domProps: { innerHTML: this.text } })
console.log('渲染函数执行了')
return vdom
},
})
app.text = '你好'
</script>
看浏览器控制台:

说明当我们修改数据后,Vue 会重新生产虚拟 DOM,然后根据这个虚拟 DOM 对真实 DOM 进行更新。
DOM 异步更新
在 Vue 中,我们是通过操作数据(虚拟 DOM)从而实现对 DOM 的操作的。这就意味着我们如果更改了数据,对应的 DOM 应该就会跟着改变。但需要注意的是:DOM 的更新是异步的,这就意味着,并不是你修改了数据,其对应的真实的 DOM 就会立马完成更新。
Vue 的官方文档是这样说的:
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。
可以通过代码验证:
html
<div id="app">{{ text }}</div>
<script>
var vm = new Vue({
el: '#',
data: {
text: '我是旧值',
},
})
vm.text = '我是新值' // 更改了数据
console.log(vm.$el.textContent) // 打印值: 我是旧值
</script>
虽然数据已经修改了,但修改后立刻访问 DOM,仍是旧值。也代表着我们获取的不是最新的 DOM,如何才能在 DOM 完成更新后操作 DOM 呢?这就需要使用到 Vue 提供的 API,即 Vue.nextTick。
Vue.nextTick
这个是 Vue 提供的 API,它可以在 DOM 更新完毕之后执行一个回调。也就是说这个 API 能够检测 DOM 是否完成了更新,然后在 DOM 更新后执行回调。
html
<div id="app">{{ text }}</div>
<script>
var vm = new Vue({
el: '#app,
data: {
text: '我是旧值'
}
})
vm.text = '我是新值' // 更改了数据
Vue.nextTick(function(){
console.log(vm.$el.textContent) //打印值: 我是新值
})
</script>
所以,官网也说过,mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick。