VueDOM 更新是异步的,VuenextTick 方法会在 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/setIntervalPromise 等。
一旦执行栈中的所有同步任务执行完毕,引擎就会处理任务队列中的内容,依次执行那些事件所对应的回调函数。


Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。