$nextTick与迭代协议

139 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

为什么 nextTick 中就能拿到 DOM 更新后的数据了?

文档

简介:当在我们 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。我们可以传递一个回调函数作为参数,或者 await 返回的 Promise。

<script>
  import { nextTick } from 'vue'

  export default {
    data() {
      return {
        count: 0,
      }
    },
    methods: {
      async increment() {
        this.count++

        console.log(document.getElementById('counter').textContent) // 0

        nextTick(() => {
          console.log(document.getElementById('counter').textContent) // 1
        })
      },
    },
  }
</script>

<template>
  <button id="counter" @click="increment">{{ count }}</button>
</template>

代码执行过程

出于性能的考虑,每次数据变化,DOM 并不会立即更新,精简过程如下。

1. 修改 Vue 中的 Data 时,Vue 会将所有和这个 Data 相关的 Watcher 加入到队列 Queue。

2. 然后,调用 nextTick 方法,传入用于 DOM 更新的回调函数。

nextTick(flushSchedulerQueue)

3. 执行 nextTick 时,碰到异步代码,会把更新 DOM 的操作添加到异步任务队列。

// 伪代码
functon nextTick(callback) {
  // 其实 nextTick 中就封装了相关的异步代码
  Promise.resolve().then(callback)
}

4. 等当前执行栈中的同步代码执行完毕后,才会把任务队列中用于 DOM 更新的异步代码拿到执行栈中执行,所以【直接获取 DOM 的同步操作】去拿到【异步更新 DOM 中的数据的操作】是办不到的。

5. 接下来再次碰到自己写的 nextTick,也会把对应的回调添加到任务队列。

nextTick(function () {
  console.log(document.getElementById('counter').textContent) // 1
})

6. 根据先进先出的原则,包含 DOM 更新操作的异步任务会先得到执行,执行完成后就生成了新的 DOM,接下来再次执行下一个异步任务时当然就能拿到更新后的 DOM 啦。

迭代接口/迭代协议(本次简述,后期会详细介绍)

目的:为所有数据结构提供一种统一的遍历机制,即 for/of 循环。

  • 概念:本质上是一个具有特定规范方法,这个方法要返回一个具有 next 方法的对象(又称为迭代器对象),每次调用 next 方法又可以拿到一个对象,这个对象的 value 就是数据结构中对应的值。
  • 每一个可迭代对象都对应着一个可迭代接口[Symbol.iterator];[Symbol.iterator]接口并不是迭代器,他是一个迭代器工厂函数,调用该迭代接口即可返回一个待执行状态的迭代器;

而不同的原生全局对象都对应着不同的迭代器;

<script>
  const obj = {
    a: 4,
    b: 7,
    // for/of 循环消费的就是默认的迭代接口 `[Symbol.iterator]`,它的执行过程如下。
    // 1. 自动调用此迭代接口。
    // 2. 得到迭代器对象后依次调用 next 方法得到对象。
    // 3. 把对象的 value 作为 for/of 循环时候的 v。
    [Symbol.iterator]() {
      let startIndex = 0
      const arr = Object.values(this)
      return {
        next() {
          if (startIndex < arr.length) {
            return {
              value: arr[startIndex++],
              done: false,
            }
          } else {
            return {
              value: undefined,
              done: true,
            }
          }
        },
      }
    },
  }

  for (let v of obj) {
    console.log(v)
  }
</script>

image.png