Vue 的异步更新机制

Vue 的异步更新机制

💡 原文英文,约1600词,阅读约需6分钟。
📝

内容提要

Vue 的异步更新机制通过队列避免频繁渲染:数据变化触发 watcher 后,queueWatcher 将其加入队列并去重,再通过 nextTick 异步执行 flushSchedulerQueue,按 id 排序后依次运行 watcher。nextTick 优先使用 Promise、MessageChannel 等创建微任务或宏任务,确保同一 tick 内多次更新只渲染一次。

🔎

延伸解读

异步更新如何避免重复渲染

Vue 的异步更新机制通过队列去重和延迟执行,确保同一 tick 内多次数据变更只触发一次渲染。例如连续赋值 this.a = 1、this.b = 2 等,不会立即执行渲染,而是将 watcher 加入队列,待 nextTick 时统一处理。这避免了频繁的 DOM 操作,提升了性能。

队列排序与执行顺序的考量

flushSchedulerQueue 会按 watcher.id 排序,保证父组件先于子组件更新,用户 watcher 先于渲染 watcher。这有助于避免不必要的子组件更新,并在父组件销毁子组件时跳过其 watcher。但需注意,在 watch 回调中无法访问最新 DOM,因为渲染 watcher 最后执行,且在同一 tick 内完成。

nextTick 的降级策略与微任务选择

nextTick 优先使用微任务(如 Promise),但在非 DOM 环境(如 Weex)或兼容性考虑下,会降级为宏任务(MessageChannel、setImmediate、setTimeout)。早期版本因微任务优先级过高可能打断事件冒泡,故采用 MessageChannel 作为折中。这种策略平衡了执行时机与浏览器兼容性。

两个队列的协作与设计意图

Vue 内部有两个队列:queueWatcher 中的 watcher 队列和 nextTick 中的 callbacks 队列。watcher 队列在 flushSchedulerQueue 执行期间仍可接受新 watcher,实现动态更新;而 callbacks 队列在等待期间不会新增,通常只包含一个 flushSchedulerQueue 函数。这种设计确保了更新流程的连贯性和高效性。

❓

Q&A

Vue 为什么需要异步更新机制?

Vue 的异步更新机制主要是为了避免频繁渲染。当数据连续变化时,如果每次赋值都立即触发视图更新,会导致页面性能下降。通过将更新函数放入队列并异步执行,可以确保同一 tick 内多次更新只渲染一次,从而提升性能。

Vue 的 queueWatcher 函数是如何工作的?

queueWatcher 函数接收一个 watcher,首先检查该 watcher 是否已加入队列(通过 has 对象去重)。如果队列未开始处理(!flushing),则直接将 watcher 推入队列;如果队列正在处理,则根据 watcher.id 将其插入到队列中合适的位置。最后,如果尚未等待刷新(!waiting),则设置 waiting 为 true,并调用 nextTick(flushSchedulerQueue) 来异步执行队列。

flushSchedulerQueue 函数的作用是什么?

flushSchedulerQueue 函数负责处理 watcher 队列。它首先将队列按 watcher.id 排序,确保父组件先于子组件、用户 watcher 先于渲染 watcher 执行。然后遍历队列,依次执行每个 watcher 的 run 方法,并更新 has 对象。执行完毕后,重置调度状态,并调用 activated 和 updated 钩子。

Vue 的 nextTick 是如何实现异步调度的?

nextTick 通过一个立即执行函数返回 queueNextTick 函数。它维护一个 callbacks 数组和 pending 标志。当调用 nextTick(cb) 时,会将回调推入 callbacks,如果 pending 为 false,则设置 pending 为 true 并调用 timerFunc 来异步执行 nextTickHandler。timerFunc 根据环境选择 setImmediate、MessageChannel、Promise 或 setTimeout 来创建异步任务。

Vue 中 nextTick 的异步任务有哪些创建方式?

Vue 提供了四种创建异步任务的方式:1. setImmediate(如果支持);2. MessageChannel(用于 IE 10 及以上,且比 Promise 支持更广);3. Promise(用于非 DOM 环境如 Weex);4. setTimeout 0(用于 IE 10 及以下)。这些方式按优先级选择,以确保回调在合适的时机执行。

在 watch 回调中能否访问到最新的 DOM?为什么?

不能。因为 watch 回调与组件渲染函数在同一个 tick 中执行,且由于 flushSchedulerQueue 中的排序,渲染 watcher 总是最后运行。因此,在 watch 回调执行时,DOM 尚未更新。如果需要访问更新后的 DOM,应使用 nextTick。

🏷️

标签

➡️

继续阅读