虚拟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 算法正确识别节点的关键
📝 发现内容有误?点击此处直接编辑