持续创作,加速成长!这是我参与「掘金日新计划 · 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>