Skip to content
文章目录

为啥nextTick能拿到更新后的dom

首先明确:DOM Tree 的更新是实时的, dom tree 的更新和 dom tree 的渲染是分开执行的, dom tree 的渲染必须等 dom tree 更新完毕之后才执行

DOM Tree 的更新是实时的, 上一行代码操作完 dom,下一行代码就能拿到最新 dom

INFO

DOM Tree 的更新是实时的

DOM Tree 的更新是实时的

DOM Tree 的更新是实时的

上面的话是什么意思?

这说明,对 DOM 的操作是能够实时得到反馈的,上一行代码操作了 DOM,下一行就能获取到

这说明,对 DOM 的操作是能够实时得到反馈的,上一行代码操作了 DOM,下一行就能获取到

这说明,对 DOM 的操作是能够实时得到反馈的,上一行代码操作了 DOM,下一行就能获取到

验证

更新完 dom 之后在下一个方法中就能立刻最新 dom

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="hello">你好</div>
    <script>
      function test() {
        var newDom = document.getElementById('hello')
        // 你可以拿到最新的innerText: 世界
        console.log(newDom.innerText)
      }

      var helloDom = document.getElementById('hello')
      console.log(helloDom.innerText) // 这里是:你好

      helloDom.innerText = '世界'
      test()
    </script>
  </body>
</html>

更新完 dom 之后,在下一行代码就能立刻拿到最新 dom

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="hello">你好</div>
    <script>
      var helloDom = document.getElementById('hello')
      //   console.log(helloDom.innerText) // 这里是:你好

      helloDom.innerText = '世界'
      var newHelloDom = document.getElementById('hello')
      console.log(newHelloDom.innerText)
    </script>
  </body>
</html>

dom tree 的更新和 dom tree 的渲染是分开执行的,dom tree 的渲染必须等 dom tree 更新完毕之后才执行

验证

将下面的代码放到浏览器的控制台执行,会依次打印如下内容,但背景颜色只会变一次(变成灰色)

black
red
blue
grey
js
document.body.style = 'background:black'
console.log(document.body.style.background) // 打印: black

document.body.style = 'background:red'
console.log(document.body.style.background) // 打印: red

document.body.style = 'background:blue'
console.log(document.body.style.background) // 打印: blue

document.body.style = 'background:grey'
console.log(document.body.style.background) // 打印: grey

dom 更新完毕才执行渲染?

更确切的说,dom 渲染是在每次宏任务执行完毕之后都会执行一次

验证

下面这段代码,放到浏览器的控制台执行,浏览器界面会先变红,然后再变黑,再变蓝色

js
// 这里记作宏任务A
document.body.style = 'background:red'
setTimeout(function () {
  // 这里记作宏任务B
  document.body.style = 'background:black'
  // 宏任务B到这里就执行完毕了,此时会进行一次dom渲染
}, 1000)
setTimeout(function () {
  // 这里记作宏任务C
  document.body.style = 'background:blue'
  // 宏任务C到这里就执行完毕了,此时会进行一次dom渲染
}, 2000)
// 宏任务A到这就执行完毕了,此时会进行一次dom渲染

那么微任务和 dom 渲染谁先执行呢?

从下面的执行结果中可以得出结论,dom 渲染会在微任务全部执行完毕之后才执行。

因为会先输出1, 再输出4, 等一会之后才会输出2, 再等一会才会输出3, 这期间背景色一直都会是白色,直到3输出完毕,才会变成红色

js
document.body.style = 'background:blue'
console.log(1)
Promise.resolve().then(function () {
  var str = ''
  for (let i = 0; i < 10000000; i++) {
    str += i
  }
  console.log(2)
  document.body.style = 'background:black'
})
Promise.resolve().then(function () {
  var str = ''
  for (let i = 0; i < 10000000; i++) {
    str += i
  }
  console.log(3)
  document.body.style = 'background:red'
})
console.log(4)

上面说的和 nextTick 有什么关系?

表明 nextTick 中传入的回调函数并非立刻执行的,而是等到 vue 将 dom 更新完毕之后再统一执行。dom 更新完毕,就能直接拿到最新的 dom 数据,而不用什么特别的手段

表明 nextTick 中传入的回调函数并非立刻执行的,而是等到 vue 将 dom 更新完毕之后再统一执行。dom 更新完毕,就能直接拿到最新的 dom 数据,而不用什么特别的手段

表明 nextTick 中传入的回调函数并非立刻执行的,而是等到 vue 将 dom 更新完毕之后再统一执行。dom 更新完毕,就能直接拿到最新的 dom 数据,而不用什么特别的手段

那 nextTick 做了什么?

可以简单的理解为:nextTick 只是收集你传入的回调函数,将其放入callbacks数组中, 他本身并不会直接执行你传入的回调函数

为什么有时候从 nextTick 回调函数中又拿不到最新的 dom 数据?

INFO

在 template 中使用到的 data 会作为组件的依赖被收集起来,所以当我们对组件中响应的数据做了修改,会触发 setter 的逻辑,从而通知这个依赖的所有 Watcher 触发一次 update。这个 update 走到最后会调用一个 nextTick(flushSchedulerQueue),这里就是为什么我们能在 nextTick 访问到更新后的 dom 的关键

vue
<!--

@file: index.vue
@author: pan
-->
<script lang="ts">
export default {
  name: 'Index',
}
</script>
<script setup lang="ts">
import { ref, nextTick } from 'vue'

const list = ref<string[]>([])
const loading = ref<boolean>(true)

setTimeout(() => {
  list.value = ['a', 'b', 'c']
  // 在这里将loading.value设置为false,nextTick中能拿到最新的dom
  // loading.value = false
  nextTick(() => {
    console.log('dom', document.getElementById('dom-1'))
  })
  // 在这里将loading.value设置为false,nextTick中拿不到最新的dom
  // loading.value = false
}, 1000)
</script>

<template>
  <div>
    <h3 v-if="loading">数据加载中</h3>
    <template v-else>
      <h3 v-for="(item, idx) in list" :id="`dom-${idx}`" :key="idx">
        {{ item }}
      </h3>
    </template>
  </div>
</template>

<style lang="scss" scoped></style>

参考资料

10 分钟了解 Vue nextTick 获取更新后 DOM 的原理

nexttick 是怎么可以获取到更新后的 dom 的_记一个 nextTick 引发的 bug

前端宏任务、微任务、Dom 渲染的顺序

Vue 响应式原理-理解 Observer、Dep、Watcher

当数据改变时,VUE 是如何实现 DOM 更新的?