主题
虚拟DOM
1. 什么是虚拟DOM
虚拟DOM(Virtual DOM)是对真实DOM的一种轻量级描述,本质上是一个JavaScript对象。它的诞生是为了解决频繁操作DOM导致的性能问题。
1.1 基本结构
js
// 虚拟DOM节点的基本结构
{
tag: 'div', // 标签名
props: { // 属性
class: 'container',
id: 'app'
},
children: [ // 子节点
{
tag: 'p',
props: {},
children: ['Hello Virtual DOM']
}
]
}
2. 工作原理
2.1 三个核心步骤
- 用JavaScript对象模拟DOM树(createElement)
- 比较新旧虚拟DOM树的差异(diff)
- 将差异应用到真实DOM(patch)
2.2 Virtual DOM的创建
js
// Vue2中创建虚拟DOM
function createElement(tag, props = {}, children = []) {
return {
tag,
props,
children
}
}
// 使用示例
const vnode = createElement('div',
{ class: 'container' },
[
createElement('p', {}, ['Hello'])
]
)
3. Diff算法
3.1 基本原则
- 同层比较,不跨层级
- 同类型比较
- key的重要性
3.2 Diff过程
js
function patch(oldVnode, newVnode) {
// 1. 不是同一个节点,直接替换
if (!sameVnode(oldVnode, newVnode)) {
return replaceNode(oldVnode, newVnode)
}
// 2. 是同一个节点,更新属性
if (newVnode.text && newVnode.text !== oldVnode.text) {
updateTextContent(oldVnode, newVnode.text)
} else {
updateChildren(oldVnode.children, newVnode.children)
}
}
function sameVnode(a, b) {
return a.key === b.key && a.tag === b.tag
}
3.3 列表Diff
js
// 带key的列表更新
function updateChildren(oldCh, newCh) {
let oldStartIdx = 0
let oldEndIdx = oldCh.length - 1
let newStartIdx = 0
let newEndIdx = newCh.length - 1
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// 头尾指针比较算法
}
}
4. Vue3的优化
4.1 静态标记
js
// Vue3中的静态节点标记
const vnode = createVNode('div', null, [
createVNode('span', null, 'static text', PatchFlags.TEXT)
])
4.2 Block树
- 只追踪动态节点
- 减少比较范围
js
// Block树示例
const block = createBlock('div', null, [
createVNode('div', { class: 'static' }),
createVNode('div', { class: dynamic }, null, 8 /* PROPS */)
])
5. 性能优化策略
5.1 编码层面
- 使用key进行身份标识
- 避免不必要的节点嵌套
- 合理使用v-show和v-if
- 使用keep-alive缓存组件
5.2 框架层面
js
// 1. 静态节点提升
const hoisted = createVNode('div', { class: 'static' })
// 2. 事件缓存
const cached = cache(() => props.onClick)
// 3. 片段(Fragment)优化
const Fragment = Symbol('Fragment')
6. 实际应用
6.1 常见场景
- 大量数据渲染
- 频繁数据更新
- 复杂组件更新
6.2 最佳实践
js
// 1. 大列表渲染优化
<template>
<div>
<div v-for="item in list" :key="item.id">
{{ item.text }}
</div>
</div>
</template>
// 2. 组件更新优化
export default {
name: 'OptimizedComponent',
props: {
data: Object
},
// 使用计算属性缓存
computed: {
processedData() {
return expensive(this.data)
}
}
}
7. 注意事项
- 虚拟DOM不一定比直接操作DOM快
- 合理使用key提高diff效率
- 避免过度优化
- 理解运行时和编译时优化的区别
8. 调试与工具
- Vue Devtools
- 性能分析工具
- 浏览器开发者工具
- 内存分析工具