Vue 重点知识复习(二)

0 阅读5分钟

虚拟 DOM 和 diff 算法

Vue 采用了 虚拟 DOM 和 diff 算法 来大幅提高渲染性能,这是 Vue 框架性能优化的核心机制。

虚拟 DOM

虚拟 DOM 是用 JavaScript 对象模拟 DOM 树结构(包含节点类型、属性和子节点等信息)的轻量级表示。

工作原理

数据变化 → 创建新 VNode 树 → diff 算法对比 → 最小化 DOM 更新

关键特性

  • 在 Vue 中称为 VNode,是描述 DOM 节点的 JavaScript 对象
  • 数据变化时,Vue 创建新的 VNode 树
  • 通过 diff 算法对比新旧 VNode 树,只更新变化的部分到真实 DOM
  • 避免了对整个 DOM 树的重绘,显著提升性能

优缺点分析

优点

  • 跨平台兼容(可渲染到原生、WebGL 等)
  • 性能优化(批量更新、最小化 DOM 操作)
  • 便于测试和调试

缺点

  • 初始化有额外开销
  • 简单场景性能不如直接操作 DOM
  • 增加了代码复杂度

diff 算法

diff 算法 用于找出两个树形结构之间的差异,是虚拟 DOM 高效更新的关键。

Vue2 双端比对算法

核心流程

  1. 比较新旧节点的首尾四个节点
  2. 若匹配则移动或更新,指针向中间收缩
  3. 若不匹配则查找,找到后移动节点
  4. 最后处理剩余节点(新增或删除)

Vue3 最长递增子序列算法

优化原理

  • 找出不需要移动的节点序列(最长递增子序列)
  • 只移动剩余需要调整位置的节点
  • 大幅减少 DOM 移动操作

key 的关键作用

  • 唯一标识:确保节点身份的稳定性
  • 精准匹配:快速定位相同节点,避免不必要的创建/销毁
  • 避免副作用:防止就地复用导致的状态错误

key 的最佳实践

为什么不建议用数组下标做列表的 key?

核心风险:使用数组下标作为 key 会破坏节点的稳定性,导致错误的 DOM 更新和性能问题。

具体问题

  • 状态丢失:输入框内容、组件状态等
  • 性能下降:不必要的组件重新渲染
  • bug 隐患:列表项状态混乱

最佳实践

  • 使用与元素内容强相关的稳定唯一标识(如数据库 id)
  • 确保 key 在列表生命周期内保持不变
  • 只有当列表固定不变且无状态时,才考虑使用下标

Vue3 diff 算法 vs Vue2 双端比对

Vue3 采用了全新的 diff 算法,相比 Vue2 的双端比对有显著性能提升:

1. 最长递增子序列算法

  • Vue3 使用最长递增子序列算法优化节点移动
  • 减少不必要的 DOM 移动操作,提升更新效率

2. 静态标记

  • 编译器对静态节点进行标记
  • 更新时直接跳过静态节点,减少 diff 比对次数

3. 缓存优化

  • 缓存新旧 VNode 数组
  • 只比对数组中实际变化的 VNode,减少计算量

4. 异步删除操作

  • 动态删除操作采用异步队列合并
  • 减少 DOM 重排次数,提升渲染性能

对比总结

特性Vue2Vue3
算法核心双端比对最长递增子序列
静态节点处理无特殊处理静态标记(直接跳过)
缓存机制VNode 数组缓存
删除操作同步执行异步队列合并
性能提升-约 1.3-2 倍

nextTick 的实现原理

nextTick 是 Vue 异步更新机制的核心 API,用于在 DOM 更新完成后执行回调。

核心原理

  • 基于浏览器的异步任务队列,采用微任务优先策略
  • 数据修改时,DOM 更新操作被放入异步队列
  • nextTick 将回调推入同一队列,确保在 DOM 更新完成后执行

Vue2 vs Vue3 实现对比

特性Vue2Vue3
兼容性支持 IE9+,多层降级放弃 IE,仅支持现代浏览器
实现方式Promise → MutationObserver → setImmediate → setTimeout仅使用 Promise
复杂度复杂(多层降级)简单(单一实现)
更新队列Watcher + nextTick 混合独立 Job 队列 + nextTick 回调
性能降级逻辑有额外开销更纯粹,性能更佳

应用场景

  1. 获取更新后的 DOM:如获取元素尺寸、位置
  2. 解决时序问题:数据更新后立即操作 DOM
  3. 优化性能:批量处理 DOM 相关操作

实现细节

Vue2 降级策略逻辑

// 优先使用 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => { p.then(flushCallbacks) }
}
// 降级到 MutationObserver
else if (
    typeof MutationObserver !== 'undefined' && 
    (isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
  // ... MutationObserver 实现
}
// 降级到 setImmediate
else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => { setImmediate(flushCallbacks) }
}
// 最终降级到 setTimeout
else {
  timerFunc = () => { setTimeout(flushCallbacks, 0) }
}

核心要点总结

虚拟 DOM & diff 算法

  • 虚拟 DOM:JS 对象模拟 DOM,最小化更新,跨平台兼容
  • Vue2 diff:双端比对算法,通过首尾指针收缩找到匹配节点
  • Vue3 diff:最长递增子序列算法,减少节点移动操作

key 的使用原则

  • 必须唯一且稳定,与元素内容强相关
  • 禁止在动态列表中使用数组下标
  • 推荐使用数据库 id 作为 key 值

Vue3 性能优化

  • 最长递增子序列算法优化节点移动
  • 静态标记跳过无变化的静态节点
  • VNode 数组缓存减少比对计算量
  • 异步删除操作合并减少 DOM 重排

nextTick 关键要点

  • 基于浏览器异步任务队列,微任务优先执行
  • 确保 DOM 更新完成后执行回调函数
  • Vue2 采用多层降级策略确保兼容性
  • Vue3 仅使用 Promise 实现,更简洁高效

学习提示

理解虚拟 DOM

  • 核心是最小化真实 DOM 操作
  • 权衡初始化开销与更新性能
  • 适合复杂应用场景,简单场景可能性能更差

掌握 diff 算法思想

  • Vue2 双端比对的四指针策略
  • Vue3 最长递增子序列的优化原理
  • key 在节点复用中的关键作用

正确使用 key

  • 动态列表必须使用稳定唯一标识
  • 避免使用会随列表变化的属性作为 key
  • 理解就地复用的潜在风险

深入 nextTick

  • 微任务与宏任务的执行顺序
  • Vue 异步更新队列的工作机制
  • 实际应用中解决 DOM 时序问题