Skip to content

虚拟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 三个核心步骤

  1. 用JavaScript对象模拟DOM树(createElement)
  2. 比较新旧虚拟DOM树的差异(diff)
  3. 将差异应用到真实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 基本原则

  1. 同层比较,不跨层级
  2. 同类型比较
  3. 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 编码层面

  1. 使用key进行身份标识
  2. 避免不必要的节点嵌套
  3. 合理使用v-show和v-if
  4. 使用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 常见场景

  1. 大量数据渲染
  2. 频繁数据更新
  3. 复杂组件更新

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. 注意事项

  1. 虚拟DOM不一定比直接操作DOM快
  2. 合理使用key提高diff效率
  3. 避免过度优化
  4. 理解运行时和编译时优化的区别

8. 调试与工具

  1. Vue Devtools
  2. 性能分析工具
  3. 浏览器开发者工具
  4. 内存分析工具