首先我们清楚在vue中更新数据的话,DOM的更新是异步的
<body>
<div id="app">
<span class="name">{{ name }}</span>
<button @click="updateName">更改</button>
</div>
</body>
<script src="./vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
name: '12'
},
methods: {
updateName () {
this.name = '14'
console.log(document.getElementsByClassName('name')[0].innerHTML) // 12
}
}
})
</script>
当点击更新name后获取到的span标签中的值还是12,就说明DOM是异步更新的,而异步更新就是借助nextTick这个函数,而nextTick这个函数的功能其实很简单,就是把函数包装成异步任务
const p = Promise.resolve()
export function nextTick(fn?: () => void): Promise<void> {
return fn ? p.then(fn) : p
}
当进行数据的更改时,是执行的同步代码,他会通知所有管理这个数据的DOM进行更新,但不是同步更新的,他会用一个队列来维护要更新的值并进行去重处理,比如下列代码
{{num}}
for(let i=0; i<100000; i++){
num = i
}
每次设置值的时候,就会往队列中添加要更新的值并进行了去重处理,而且当往队列中第一次添加值的时候,其实就已经开启了更新DOM的异步任务,当所有的值设置完之后,异步任务才开始执行(涉及到主线程中的任务和异步任务的执行顺序),所以说不管num设置多少次,始终只会执行一次更新,而一般我们使用nextTick能够获取到更新后的DOM,就是因为它也是异步任务并且是在更新DOM的异步任务执行完之后再执行的所以能够获取最新的DOM
下面这种情况就可以证实
<body>
<div id="app">
<span class="name">{{ name }}</span>
<button @click="updateName">更改</button>
</div>
</body>
<script src="./vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
name: '12'
},
methods: {
updateName () {
this.$nextTick(() => {
console.log(document.getElementsByClassName('name')[0].innerHTML) // 12
})
this.name = '14'
this.name = '15'
}
}
})
</script>
这样获取到的值就是12,因为更新DOM的异步任务要在设置值的时候才会执行,所以这时候$nextTick的异步任务就排在了更新DOM的异步任务的前面所以就会先执行
tips:每次重新设置不同值的时候都会像队列中添加值然后执行更新DOM的异步任务,但它其实有一个状态来管理是否已经开启了,所以并不会重复调用
function queueFlush() {
// 避免重复调用flushJobs
if (!isFlushing && !isFlushPending) {
isFlushPending = true
nextTick(flushJobs)
}
}