全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-20 7 分钟 ✍️ juanwangdev

虚拟DOM与Diff算法

虚拟DOM(VNode)是真实DOM的轻量级JavaScript对象表示,Vue通过对比新旧VNode树来最小化DOM操作。

VNode 结构

JavaScript
class VNode {
  constructor(tag, data, children, text, elm) {
    this.tag = tag        // 标签名 'div', 'span'
    this.data = data      // 属性、事件等
    this.children = children  // 子VNode数组
    this.text = text      // 文本节点内容
    this.elm = elm        // 对应的真实DOM元素
  }
}

// 创建VNode
const vnode = new VNode('div', { id: 'app' }, [
  new VNode('span', {}, ['Hello'])
])

render 函数生成 VNode

JavaScript
// 模板编译后
render(h) {
  return h('div', { id: 'app' }, [
    h('span', { class: 'text' }, this.message),
    h('button', { on: { click: this.handleClick } }, '点击')
  ])
}

Diff 算法核心

Vue 的 Diff 算法采用同层比较策略,只对比相同层级的节点:

1. 节点对比

JavaScript
function sameVnode(a, b) {
  return (
    a.key === b.key &&
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data)
  )
}

2. 双端比较(Vue 2)

新旧子节点数组各有首尾两个指针,共四种匹配可能:

JavaScript
function updateChildren(parentElm, oldCh, newCh) {
  let oldStartIdx = 0, newStartIdx = 0
  let oldEndIdx = oldCh.length - 1, newEndIdx = newCh.length - 1
  
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 旧首 vs 新首
    if (sameVnode(oldStartVnode, newStartVnode)) {
      patchVnode(oldStartVnode, newStartVnode)
      oldStartIdx++; newStartIdx++
    }
    // 旧尾 vs 新尾
    else if (sameVnode(oldEndVnode, newEndVnode)) {
      patchVnode(oldEndVnode, newEndVnode)
      oldEndIdx--; newEndIdx--
    }
    // 旧首 vs 新尾(移动节点)
    else if (sameVnode(oldStartVnode, newEndVnode)) {
      patchVnode(oldStartVnode, newEndVnode)
      // 将旧首节点移到末尾
      oldStartIdx++; newEndIdx--
    }
    // 旧尾 vs 新首(移动节点)
    else if (sameVnode(oldEndVnode, newStartVnode)) {
      patchVnode(oldEndVnode, newStartVnode)
      oldEndIdx--; newStartIdx++
    }
    // 都不匹配,通过key查找
    else {
      // 使用key索引优化查找
    }
  }
}

3. Vue 3 的最长递增子序列

Vue 3 优化了 Diff 算法,使用最长递增子序列(LIS)减少移动操作:

JavaScript
// 获取需要移动的节点索引
const increasingSeq = getLongestIncreasingSubsequence(keyToNewIndexMap)
// 只在序列外的节点需要移动
for (let i = newChildren.length - 1; i >= 0; i--) {
  if (!increasingSeq.includes(i)) {
    moveNode(newChildren[i])
  }
}

key 的重要性

JavaScript
// 错误:没有 key,Diff 会就地复用导致状态错乱
<div v-for="item in list">{{ item.name }}</div>

// 正确:唯一 key 帮助 Diff 准确追踪节点
<div v-for="item in list" :key="item.id">{{ item.name }}</div>

要点总结

  • VNode 是 DOM 的轻量 JS 表示,创建和对比成本低
  • Diff 算法采用同层比较,时间复杂度 O(n)
  • Vue 2 使用双端比较(四种匹配情况)
  • Vue 3 引入最长递增子序列优化,减少不必要的 DOM 移动
  • key 是 Diff 算法正确识别节点的关键

📝 发现内容有误?点击此处直接编辑

← 上一篇 组件渲染优化
下一篇 → Diff算法与Patch过程
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库