Virtual DOM实现详解

本文目标

学完本文,你将能够:

  • 理解Virtual DOM的设计初衷和核心价值
  • 掌握VNode节点的类型系统和数据结构
  • 实现完整的Virtual DOM创建、比较和更新机制
  • 理解createElement和patch函数的工作原理
  • 掌握Virtual DOM的性能优化策略

系列导航

上一篇: 依赖收集机制 | 下一篇: 模板编译详解

目录

  1. 为什么需要Virtual DOM
  2. VNode的设计与实现
  3. createElement函数详解
  4. patch算法核心实现
  5. 性能分析与优化
  6. 实战应用与最佳实践

为什么需要Virtual DOM

在传统的Web开发中,我们直接操作DOM来更新页面。然而,这种方式存在严重的性能问题:

// 传统DOM操作的性能问题演示
console.time('直接DOM操作');
const container = document.getElementById('app');

// 更新1000个列表项
for (let i = 0; i < 1000; i++) {
  const item = container.children[i];
  if (item) {
    // 每次操作都会触发重排和重绘
    item.textContent = `Item ${i} - Updated`;
    item.style.color = i % 2 === 0 ? 'red' : 'blue';
  }
}
console.timeEnd('直接DOM操作');

DOM操作的性能瓶颈

  1. 频繁的重排(Reflow)和重绘(Repaint)
  2. DOM API调用开销大
  3. 缺乏批量更新机制
  4. 难以进行差异化更新
graph LR A[JavaScript操作] --> B[DOM API调用] B --> C[浏览器解析] C --> D[样式计算] D --> E[布局/重排] E --> F[绘制/重绘] F --> G[合成] G --> H[显示]

Virtual DOM的解决方案

Virtual DOM通过在JavaScript和真实DOM之间建立一个虚拟层,将多次DOM操作转换为一次批量更新:

graph TB A[组件状态变化] --> B[生成新的Virtual DOM树] B --> C[与旧Virtual DOM树对比] C --> D[计算最小差异集] D --> E[批量更新真实DOM] subgraph "Virtual DOM层" B C D end subgraph "真实DOM层" E end

VNode的设计与实现

VNode类型系统

Virtual DOM的核心是VNode(虚拟节点),它是真实DOM节点的JavaScript对象表示:

// VNode的核心类型定义
class VNode {
  constructor(
    tag,        // 标签名或组件
    data,       // 节点数据(属性、事件等)
    children,   // 子节点
    text,       // 文本内容
    elm,        // 对应的真实DOM
    context,    // 组件实例
    key         // 节点标识
  ) {
    this.tag = tag;
    this.data = data;
    this.children = children;
    this.text = text;
    this.elm = elm;
    this.context = context;
    this.key = data && data.key;
    this.componentOptions = undefined;
    this.componentInstance = undefined;
    this.parent = undefined;
    
    // 标记节点类型
    this.isStatic = false;
    this.isComment = false;
    this.isCloned = false;
  }
}

// VNode类型枚举
const VNodeTypes = {
  ELEMENT: 'ELEMENT',           // 元素节点
  TEXT: 'TEXT',                 // 文本节点
  COMPONENT: 'COMPONENT',       // 组件节点
  FUNCTIONAL: 'FUNCTIONAL',     // 函数式组件
  COMMENT: 'COMMENT',           // 注释节点
  FRAGMENT: 'FRAGMENT'          // 片段节点
};

VNode工厂函数

为了方便创建不同类型的VNode,我们实现一系列工厂函数:

// 创建元素VNode
function createElementVNode(tag, data, children) {
  return new VNode(tag, data, normalizeChildren(children));
}

// 创建文本VNode
function createTextVNode(text) {
  return new VNode(undefined, undefined, undefined, String(text));
}

// 创建注释VNode
function createCommentVNode(text) {
  const node = new VNode();
  node.text = text;
  node.isComment = true;
  return node;
}

// 创建空VNode
function createEmptyVNode() {
  const node = new VNode();
  node.text = '';
  node.isComment = true;
  return node;
}

// 克隆VNode
function cloneVNode(vnode) {
  const cloned = new VNode(
    vnode.tag,
    vnode.data,
    vnode.children && vnode.children.slice(),
    vnode.text,
    vnode.elm,
    vnode.context,
    vnode.componentOptions
  );
  cloned.key = vnode.key;
  cloned.isCloned = true;
  return cloned;
}

// 规范化子节点
function normalizeChildren(children) {
  if (typeof children === 'string') {
    return [createTextVNode(children)];
  } else if (Array.isArray(children)) {
    return children.map(child => {
      if (typeof child === 'string') {
        return createTextVNode(child);
      }
      return child;
    });
  }
  return [];
}

createElement函数详解

createElement函数(在Vue中也称为h函数)是创建VNode的核心API:

// 完整的createElement实现
function createElement(context, tag, data, children) {
  // 参数规范化
  if (Array.isArray(data)) {
    children = data;
    data = undefined;
  }
  
  if (!tag) {
    return createEmptyVNode();
  }
  
  // 处理组件
  if (typeof tag === 'object') {
    return createComponentVNode(tag, data, children, context);
  }
  
  // 处理普通HTML标签
  if (typeof tag === 'string') {
    // 保留标签处理
    if (isReservedTag(tag)) {
      return createElementVNode(
        tag,
        data,
        normalizeChildren(children)
      );
    }
    
    // 组件标签处理
    const Ctor = resolveAsset(context, 'components', tag);
    if (Ctor) {
      return createComponentVNode(Ctor, data, children, context);
    }
    
    // 未知标签
    return createElementVNode(tag, data, children);
  }
  
  // 处理函数式组件
  if (typeof tag === 'function') {
    return createFunctionalComponent(tag, data, children, context);
  }
}

// 处理VNode数据
function processVNodeData(data) {
  const processedData = {};
  
  // 处理class
  if (data.class) {
    processedData.class = normalizeClass(data.class);
  }
  
  // 处理style
  if (data.style) {
    processedData.style = normalizeStyle(data.style);
  }
  
  // 处理attributes
  if (data.attrs) {
    processedData.attrs = data.attrs;
  }
  
  // 处理事件
  if (data.on) {
    processedData.on = data.on;
  }
  
  // 处理props
  if (data.props) {
    processedData.props = data.props;
  }
  
  // 处理directives
  if (data.directives) {
    processedData.directives = data.directives;
  }
  
  return processedData;
}

// 使用示例
const vnode = createElement('div', {
  class: 'container',
  style: { color: 'red' },
  on: { click: handleClick }
}, [
  createElement('h1', 'Hello Virtual DOM'),
  createElement('p', 'This is a paragraph'),
  createElement('ul', [
    createElement('li', 'Item 1'),
    createElement('li', 'Item 2'),
    createElement('li', 'Item 3')
  ])
]);

patch算法核心实现

patch算法是Virtual DOM的核心,负责将VNode渲染为真实DOM,并高效地更新DOM:

// patch函数主体
function patch(oldVnode, vnode, parentElm) {
  // 如果新节点不存在,删除旧节点
  if (!vnode) {
    if (oldVnode) removeVnodes([oldVnode], 0, 0);
    return;
  }
  
  // 如果旧节点不存在,创建新节点
  if (!oldVnode) {
    createElm(vnode, parentElm);
    return;
  }
  
  // 判断是否为相同节点
  if (sameVnode(oldVnode, vnode)) {
    // 更新现有节点
    patchVnode(oldVnode, vnode);
  } else {
    // 替换节点
    const oldElm = oldVnode.elm;
    const parentElm = oldElm.parentNode;
    
    createElm(vnode, parentElm, oldElm.nextSibling);
    removeVnodes([oldVnode], 0, 0);
  }
  
  return vnode.elm;
}

// 判断是否为相同节点
function sameVnode(a, b) {
  return (
    a.key === b.key &&
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
  );
}

// 创建真实DOM
function createElm(vnode, parentElm, refElm) {
  // 尝试创建组件
  if (createComponent(vnode, parentElm, refElm)) {
    return;
  }
  
  const data = vnode.data;
  const children = vnode.children;
  const tag = vnode.tag;
  
  if (tag) {
    // 创建元素节点
    vnode.elm = document.createElement(tag);
    
    // 设置属性
    setAttrs(vnode);
    
    // 创建子节点
    createChildren(vnode, children);
    
    // 插入DOM
    insert(parentElm, vnode.elm, refElm);
  } else if (vnode.isComment) {
    // 创建注释节点
    vnode.elm = document.createComment(vnode.text);
    insert(parentElm, vnode.elm, refElm);
  } else {
    // 创建文本节点
    vnode.elm = document.createTextNode(vnode.text);
    insert(parentElm, vnode.elm, refElm);
  }
}

// 更新节点
function patchVnode(oldVnode, vnode) {
  if (oldVnode === vnode) return;
  
  const elm = vnode.elm = oldVnode.elm;
  const oldCh = oldVnode.children;
  const ch = vnode.children;
  
  // 更新属性
  updateAttrs(oldVnode, vnode);
  
  // 更新文本内容
  if (vnode.text) {
    if (oldVnode.text !== vnode.text) {
      elm.textContent = vnode.text;
    }
  } else {
    if (oldCh && ch) {
      // 更新子节点
      if (oldCh !== ch) updateChildren(elm, oldCh, ch);
    } else if (ch) {
      // 添加新子节点
      if (oldVnode.text) elm.textContent = '';
      addVnodes(elm, null, ch, 0, ch.length - 1);
    } else if (oldCh) {
      // 删除旧子节点
      removeVnodes(oldCh, 0, oldCh.length - 1);
    } else if (oldVnode.text) {
      // 清空文本
      elm.textContent = '';
    }
  }
}

// 高效的子节点更新算法
function updateChildren(parentElm, oldCh, newCh) {
  let oldStartIdx = 0;
  let newStartIdx = 0;
  let oldEndIdx = oldCh.length - 1;
  let newEndIdx = newCh.length - 1;
  let oldStartVnode = oldCh[0];
  let oldEndVnode = oldCh[oldEndIdx];
  let newStartVnode = newCh[0];
  let newEndVnode = newCh[newEndIdx];
  let oldKeyToIdx, idxInOld, vnodeToMove, refElm;
  
  // 双端比较算法
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (!oldStartVnode) {
      oldStartVnode = oldCh[++oldStartIdx];
    } else if (!oldEndVnode) {
      oldEndVnode = oldCh[--oldEndIdx];
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      // 旧头 vs 新头
      patchVnode(oldStartVnode, newStartVnode);
      oldStartVnode = oldCh[++oldStartIdx];
      newStartVnode = newCh[++newStartIdx];
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      // 旧尾 vs 新尾
      patchVnode(oldEndVnode, newEndVnode);
      oldEndVnode = oldCh[--oldEndIdx];
      newEndVnode = newCh[--newEndIdx];
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
      // 旧头 vs 新尾
      patchVnode(oldStartVnode, newEndVnode);
      parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
      oldStartVnode = oldCh[++oldStartIdx];
      newEndVnode = newCh[--newEndIdx];
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
      // 旧尾 vs 新头
      patchVnode(oldEndVnode, newStartVnode);
      parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
      oldEndVnode = oldCh[--oldEndIdx];
      newStartVnode = newCh[++newStartIdx];
    } else {
      // 使用key进行查找
      if (!oldKeyToIdx) {
        oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
      }
      idxInOld = oldKeyToIdx[newStartVnode.key];
      
      if (!idxInOld) {
        // 新节点
        createElm(newStartVnode, parentElm, oldStartVnode.elm);
      } else {
        // 移动节点
        vnodeToMove = oldCh[idxInOld];
        patchVnode(vnodeToMove, newStartVnode);
        oldCh[idxInOld] = undefined;
        parentElm.insertBefore(vnodeToMove.elm, oldStartVnode.elm);
      }
      newStartVnode = newCh[++newStartIdx];
    }
  }
  
  // 处理剩余节点
  if (oldStartIdx > oldEndIdx) {
    // 添加剩余的新节点
    refElm = newCh[newEndIdx + 1] ? newCh[newEndIdx + 1].elm : null;
    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx);
  } else if (newStartIdx > newEndIdx) {
    // 删除剩余的旧节点
    removeVnodes(oldCh, oldStartIdx, oldEndIdx);
  }
}

性能分析与优化

性能测试对比

让我们通过实际测试来比较Virtual DOM和直接DOM操作的性能差异:

// 性能测试工具类
class PerformanceTester {
  constructor() {
    this.results = [];
  }
  
  // 测试直接DOM操作
  testDirectDOM(count = 1000) {
    const container = document.createElement('div');
    document.body.appendChild(container);
    
    const start = performance.now();
    
    // 初始创建
    for (let i = 0; i < count; i++) {
      const div = document.createElement('div');
      div.className = 'item';
      div.textContent = `Item ${i}`;
      container.appendChild(div);
    }
    
    // 批量更新
    const items = container.querySelectorAll('.item');
    items.forEach((item, i) => {
      item.textContent = `Updated Item ${i}`;
      item.style.color = i % 2 === 0 ? 'red' : 'blue';
    });
    
    const end = performance.now();
    document.body.removeChild(container);
    
    return {
      name: 'Direct DOM',
      time: end - start,
      operations: count * 2
    };
  }
  
  // 测试Virtual DOM
  testVirtualDOM(count = 1000) {
    const container = document.createElement('div');
    document.body.appendChild(container);
    
    const start = performance.now();
    
    // 初始渲染
    const oldVnode = createElement('div', null, 
      Array.from({ length: count }, (_, i) => 
        createElement('div', { class: 'item' }, `Item ${i}`)
      )
    );
    patch(null, oldVnode, container);
    
    // 批量更新
    const newVnode = createElement('div', null,
      Array.from({ length: count }, (_, i) =>
        createElement('div', {
          class: 'item',
          style: { color: i % 2 === 0 ? 'red' : 'blue' }
        }, `Updated Item ${i}`)
      )
    );
    patch(oldVnode, newVnode, container);
    
    const end = performance.now();
    document.body.removeChild(container);
    
    return {
      name: 'Virtual DOM',
      time: end - start,
      operations: count * 2
    };
  }
  
  // 运行性能测试
  runBenchmark(counts = [100, 500, 1000, 5000]) {
    console.log('开始性能测试...\n');
    
    counts.forEach(count => {
      console.log(`测试规模: ${count} 个节点`);
      
      const directResult = this.testDirectDOM(count);
      const virtualResult = this.testVirtualDOM(count);
      
      console.log(`Direct DOM: ${directResult.time.toFixed(2)}ms`);
      console.log(`Virtual DOM: ${virtualResult.time.toFixed(2)}ms`);
      console.log(`性能提升: ${((directResult.time - virtualResult.time) / directResult.time * 100).toFixed(2)}%\n`);
      
      this.results.push({
        count,
        direct: directResult.time,
        virtual: virtualResult.time
      });
    });
    
    this.visualizeResults();
  }
  
  // 可视化结果
  visualizeResults() {
    console.log('性能对比图表:');
    console.log('节点数量 | Direct DOM | Virtual DOM | 提升比例');
    console.log('---------|------------|-------------|----------');
    
    this.results.forEach(result => {
      const improvement = ((result.direct - result.virtual) / result.direct * 100).toFixed(2);
      console.log(
        `${result.count.toString().padEnd(8)} | ` +
        `${result.direct.toFixed(2).padEnd(10)}ms | ` +
        `${result.virtual.toFixed(2).padEnd(11)}ms | ` +
        `${improvement}%`
      );
    });
  }
}

// 运行测试
const tester = new PerformanceTester();
// tester.runBenchmark();

Virtual DOM性能优化策略

graph TB A[Virtual DOM性能优化] --> B[编译时优化] A --> C[运行时优化] A --> D[算法优化] B --> B1[静态节点标记] B --> B2[静态属性提升] B --> B3[内联事件处理] C --> C1[异步更新队列] C --> C2[组件级更新] C --> C3[函数式组件] D --> D1[双端比较算法] D --> D2[key值优化] D --> D3[最长递增子序列]

1. 静态节点优化

// 标记静态节点,避免重复创建
function markStatic(node) {
  node.isStatic = isStatic(node);
  
  if (node.children) {
    for (let i = 0; i < node.children.length; i++) {
      const child = node.children[i];
      markStatic(child);
      if (!child.isStatic) {
        node.isStatic = false;
      }
    }
  }
}

function isStatic(node) {
  // 文本节点
  if (node.type === 2) {
    return true;
  }
  // 表达式节点
  if (node.type === 3) {
    return false;
  }
  // 元素节点
  return !!(
    !node.hasBindings && // 没有动态绑定
    !node.if && !node.for && // 没有v-if/v-for
    !isBuiltInTag(node.tag) && // 不是内置组件
    isPlatformReservedTag(node.tag) && // 是平台保留标签
    !isDirectChildOfTemplateFor(node) &&
    Object.keys(node).every(isStaticKey)
  );
}

2. 组件级别的优化

// 组件级别的Virtual DOM优化
class OptimizedComponent {
  constructor(options) {
    this.options = options;
    this._vnode = null;
    this._staticTrees = null;
  }
  
  // 缓存静态子树
  _renderStatic(index) {
    const cached = this._staticTrees || (this._staticTrees = []);
    let tree = cached[index];
    
    if (tree) {
      return Array.isArray(tree)
        ? cloneVNodes(tree)
        : cloneVNode(tree);
    }
    
    // 渲染静态子树
    tree = this.options.staticRenderFns[index].call(this);
    cached[index] = tree;
    return tree;
  }
  
  // 优化的更新策略
  _update(vnode) {
    const prevVnode = this._vnode;
    this._vnode = vnode;
    
    if (!prevVnode) {
      // 初始渲染
      this.$el = this._patch(null, vnode);
    } else {
      // 更新:只对比组件级别的变化
      this.$el = this._patch(prevVnode, vnode);
    }
  }
  
  // 智能的shouldUpdate检查
  shouldUpdate(nextProps, nextState) {
    // 浅比较props和state
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState);
  }
}

3. key值优化策略

// 优化的key-index映射
function createKeyToOldIdx(children, beginIdx, endIdx) {
  const map = {};
  for (let i = beginIdx; i <= endIdx; i++) {
    const key = children[i].key;
    if (key !== undefined) {
      map[key] = i;
    }
  }
  return map;
}

// 使用最长递增子序列优化移动
function getSequence(arr) {
  const p = arr.slice();
  const result = [0];
  let i, j, u, v, c;
  const len = arr.length;
  
  for (i = 0; i < len; i++) {
    const arrI = arr[i];
    if (arrI !== 0) {
      j = result[result.length - 1];
      if (arr[j] < arrI) {
        p[i] = j;
        result.push(i);
        continue;
      }
      u = 0;
      v = result.length - 1;
      while (u < v) {
        c = (u + v) >> 1;
        if (arr[result[c]] < arrI) {
          u = c + 1;
        } else {
          v = c;
        }
      }
      if (arrI < arr[result[u]]) {
        if (u > 0) {
          p[i] = result[u - 1];
        }
        result[u] = i;
      }
    }
  }
  
  u = result.length;
  v = result[u - 1];
  while (u-- > 0) {
    result[u] = v;
    v = p[v];
  }
  
  return result;
}

实战应用与最佳实践

1. 合理使用key属性

// 错误:使用索引作为key
const list = items.map((item, index) => 
  createElement('li', { key: index }, item.text)
);

// 正确:使用唯一且稳定的标识
const list = items.map(item => 
  createElement('li', { key: item.id }, item.text)
);

// 展示key的重要性
class KeyDemo {
  constructor() {
    this.items = [
      { id: 1, text: 'Apple' },
      { id: 2, text: 'Banana' },
      { id: 3, text: 'Cherry' }
    ];
  }
  
  // 演示错误的key使用
  renderWithIndexKey() {
    return createElement('ul', 
      this.items.map((item, index) => 
        createElement('li', { 
          key: index,
          style: { transition: 'all 0.3s' }
        }, [
          createElement('input', { type: 'text' }),
          createElement('span', item.text)
        ])
      )
    );
  }
  
  // 演示正确的key使用
  renderWithIdKey() {
    return createElement('ul',
      this.items.map(item =>
        createElement('li', {
          key: item.id,
          style: { transition: 'all 0.3s' }
        }, [
          createElement('input', { type: 'text' }),
          createElement('span', item.text)
        ])
      )
    );
  }
  
  // 重新排序演示
  shuffle() {
    this.items = this.items.sort(() => Math.random() - 0.5);
    // 使用index作为key时,input的值会错乱
    // 使用id作为key时,input的值会正确保持
  }
}

2. 避免不必要的渲染

// 实现一个智能的组件更新系统
class SmartVirtualDOM {
  constructor() {
    this.components = new Map();
  }
  
  // 注册组件的渲染优化
  registerComponent(name, component) {
    this.components.set(name, {
      component,
      lastProps: null,
      lastVNode: null,
      renderCount: 0
    });
  }
  
  // 智能渲染
  renderComponent(name, props) {
    const record = this.components.get(name);
    if (!record) return null;
    
    // 检查是否需要重新渲染
    if (this.shouldComponentUpdate(record, props)) {
      record.renderCount++;
      record.lastProps = props;
      record.lastVNode = record.component.render(props);
      
      console.log(`Component ${name} rendered (${record.renderCount} times)`);
    } else {
      console.log(`Component ${name} render skipped`);
    }
    
    return record.lastVNode;
  }
  
  // 智能的更新检查
  shouldComponentUpdate(record, newProps) {
    if (!record.lastProps) return true;
    
    // 深度比较优化
    return !deepEqual(record.lastProps, newProps);
  }
  
  // 批量更新优化
  batchUpdate(updates) {
    console.log('开始批量更新...');
    const startTime = performance.now();
    
    // 收集所有需要更新的组件
    const updateQueue = [];
    updates.forEach(({ name, props }) => {
      const record = this.components.get(name);
      if (record && this.shouldComponentUpdate(record, props)) {
        updateQueue.push({ name, props, record });
      }
    });
    
    // 批量执行更新
    updateQueue.forEach(({ name, props, record }) => {
      record.renderCount++;
      record.lastProps = props;
      record.lastVNode = record.component.render(props);
    });
    
    const endTime = performance.now();
    console.log(`批量更新完成,耗时: ${(endTime - startTime).toFixed(2)}ms`);
    console.log(`更新组件数: ${updateQueue.length}/${updates.length}`);
  }
}

// 使用示例
const smartVDOM = new SmartVirtualDOM();

// 注册组件
smartVDOM.registerComponent('UserCard', {
  render(props) {
    return createElement('div', { class: 'user-card' }, [
      createElement('h3', props.name),
      createElement('p', props.email)
    ]);
  }
});

// 多次渲染测试
smartVDOM.renderComponent('UserCard', { name: 'John', email: '[email protected]' });
smartVDOM.renderComponent('UserCard', { name: 'John', email: '[email protected]' }); // 跳过
smartVDOM.renderComponent('UserCard', { name: 'Jane', email: '[email protected]' }); // 渲染

3. Virtual DOM的实际应用场景

// 完整的TodoList应用示例
class VirtualDOMTodoApp {
  constructor(container) {
    this.container = container;
    this.state = {
      todos: [],
      filter: 'all', // all, active, completed
      input: ''
    };
    this.vnode = null;
  }
  
  // 添加待办事项
  addTodo(text) {
    this.state.todos.push({
      id: Date.now(),
      text,
      completed: false
    });
    this.render();
  }
  
  // 切换完成状态
  toggleTodo(id) {
    const todo = this.state.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
      this.render();
    }
  }
  
  // 删除待办事项
  removeTodo(id) {
    this.state.todos = this.state.todos.filter(t => t.id !== id);
    this.render();
  }
  
  // 渲染应用
  render() {
    const newVnode = this.buildVNode();
    
    if (this.vnode) {
      // 更新现有DOM
      patch(this.vnode, newVnode);
    } else {
      // 初始渲染
      patch(null, newVnode, this.container);
    }
    
    this.vnode = newVnode;
  }
  
  // 构建Virtual DOM树
  buildVNode() {
    const { todos, filter, input } = this.state;
    
    // 过滤待办事项
    const filteredTodos = todos.filter(todo => {
      if (filter === 'active') return !todo.completed;
      if (filter === 'completed') return todo.completed;
      return true;
    });
    
    return createElement('div', { class: 'todo-app' }, [
      // 标题
      createElement('h1', 'Virtual DOM Todo App'),
      
      // 输入框
      createElement('div', { class: 'todo-input' }, [
        createElement('input', {
          type: 'text',
          placeholder: '添加待办事项...',
          value: input,
          on: {
            input: (e) => {
              this.state.input = e.target.value;
              this.render();
            },
            keyup: (e) => {
              if (e.key === 'Enter' && this.state.input.trim()) {
                this.addTodo(this.state.input.trim());
                this.state.input = '';
              }
            }
          }
        }),
        createElement('button', {
          on: {
            click: () => {
              if (this.state.input.trim()) {
                this.addTodo(this.state.input.trim());
                this.state.input = '';
              }
            }
          }
        }, '添加')
      ]),
      
      // 过滤器
      createElement('div', { class: 'filters' }, [
        ['all', 'active', 'completed'].map(f =>
          createElement('button', {
            class: filter === f ? 'active' : '',
            on: {
              click: () => {
                this.state.filter = f;
                this.render();
              }
            }
          }, f.charAt(0).toUpperCase() + f.slice(1))
        )
      ]),
      
      // 待办事项列表
      createElement('ul', { class: 'todo-list' },
        filteredTodos.map(todo =>
          createElement('li', {
            key: todo.id,
            class: todo.completed ? 'completed' : ''
          }, [
            createElement('input', {
              type: 'checkbox',
              checked: todo.completed,
              on: {
                change: () => this.toggleTodo(todo.id)
              }
            }),
            createElement('span', todo.text),
            createElement('button', {
              on: {
                click: () => this.removeTodo(todo.id)
              }
            }, '删除')
          ])
        )
      ),
      
      // 统计信息
      createElement('div', { class: 'stats' }, [
        createElement('span', `总计: ${todos.length}`),
        createElement('span', `已完成: ${todos.filter(t => t.completed).length}`),
        createElement('span', `未完成: ${todos.filter(t => !t.completed).length}`)
      ])
    ]);
  }
}

// 初始化应用
// const app = new VirtualDOMTodoApp(document.getElementById('app'));
// app.render();

核心要点总结

  1. Virtual DOM的本质:用JavaScript对象表示DOM结构,通过对比差异来最小化DOM操作

  2. 性能优势来源

    • 批量DOM更新
    • 最小化重排重绘
    • 高效的diff算法
    • 编译时优化
  3. 设计权衡

    • 内存开销换取性能提升
    • 开发效率与运行效率的平衡
    • 简单场景可能不如直接操作DOM
  4. 最佳实践

    • 合理使用key属性
    • 避免不必要的渲染
    • 利用静态节点优化
    • 组件级别的更新控制
  5. 适用场景

    • 复杂的交互式应用
    • 频繁的数据更新
    • 大量的DOM操作
    • 需要跨平台渲染

相关文章


最后更新: 2025年1月

Read more

Vue.js异步更新与nextTick机制深度解析(上篇)

Vue.js异步更新与nextTick机制深度解析(上篇)

本文目标 学完本文,你将能够: * 理解Vue.js为什么采用异步更新策略 * 掌握更新队列的设计思想和实现机制 * 深入理解Event Loop在Vue中的应用 * 了解nextTick的多种实现方式 系列导航 上一篇: Diff算法深度剖析 | 下一篇: Vue.js异步更新与nextTick机制(下篇) | 组件系统架构 引言:为什么DOM更新是异步的? 在Vue.js开发中,你可能遇到过这样的场景: // 场景1:连续修改数据 export default { data() { return { count: 0 } }, methods: { increment() { // 如果每次修改都立即更新DOM,会触发3次DOM更新 this.count++ // 触发一次? this.count++ // 触发一次? this.count++ // 触发一次? // 实际上:Vue只会触发一次DOM更新!

Vue.js组件系统架构深度解析

本文目标 学完本文,你将能够: * 理解Vue.js组件从创建到销毁的完整生命周期 * 掌握组件实例化和初始化的内部流程 * 深入理解父子组件通信的底层机制 * 学会实现完整的插槽系统(包括作用域插槽) * 掌握动态组件和异步组件的实现原理 * 应用组件级别的性能优化技巧 系列导航 上一篇: 异步更新与nextTick(下篇) | 下一篇: 状态管理模式 引言:组件是如何工作的? 在Vue.js开发中,我们每天都在使用组件。但你是否想过: // 当我们这样定义一个组件 const MyComponent = { data() { return { count: 0 } }, template: '<button @click="count++">{{ count }}</button>' } // 并使用它时 new Vue({ components: { MyComponent }, template:

Vue.js状态管理模式:构建可扩展的应用架构

本文目标 学完本文,你将能够: * 理解为什么大型应用需要状态管理 * 掌握Vuex的核心设计模式和实现原理 * 实现一个简化版的状态管理库 * 理解模块化和命名空间的设计思想 * 掌握大型应用的状态管理最佳实践 * 了解现代状态管理方案的演进 系列导航 上一篇: 组件系统架构 | 下一篇: 性能优化实践 1. 为什么需要状态管理? 1.1 组件通信的困境 在大型Vue.js应用中,组件间的通信会变得异常复杂: // 问题场景:多层级组件的状态共享 // GrandParent.vue <template> <Parent :user="user" @update-user="updateUser" /> </template> // Parent.vue <template> <Child

Vue.js依赖收集与追踪机制深度剖析

本文目标 学完本文,你将能够: * 理解Vue.js如何精确知道哪些组件需要更新 * 掌握Dep、Watcher、Observer三大核心类的协作机制 * 深入理解依赖收集的时机和完整过程 * 能够手写一个完整的依赖收集系统 * 解决实际开发中的依赖追踪问题 系列导航 上一篇: 响应式系统核心原理 | 下一篇: Virtual DOM实现详解 引言:为什么Vue知道哪些组件需要更新? 在使用Vue.js时,你是否想过这样一个问题:当我们修改一个数据时,Vue是如何精确地知道哪些组件用到了这个数据,并只更新这些组件的? // 假设有这样的场景 const app = new Vue({ data: { user: { name: 'John', age: 25 } } }); // 组件A只用到了user.name // 组件B只用到了user.age // 组件C同时用到了name和age // 当我们修改user.name时 app.user.name = 'Jane&