将虚拟 Dom 转化为真实 Dom

实现一个函数,将虚拟 DOM 对象转换为真实的 DOM 节点,这是理解前端框架渲染机制的基础

问题

虚拟 DOM 是现代前端框架(如 React、Vue)的概念之一。虚拟 DOM 本质上是用 JavaScript 对象来描述 DOM 结构,通过对比虚拟 DOM 的差异来最小化真实 DOM 的操作,从而提升性能。

本题要求实现一个函数,将虚拟 DOM 对象转换为真实的 DOM 节点。虚拟 DOM 对象通常包含以下信息:

  • tag: 标签名
  • props: 属性对象(包括 class、style、事件等)
  • children: 子节点数组(可以是虚拟 DOM 对象或文本)

解答

/**
 * 将虚拟 DOM 转换为真实 DOM
 * @param {Object} vnode - 虚拟 DOM 对象
 * @returns {HTMLElement|Text} 真实 DOM 节点
 */
function render(vnode) {
  // 处理文本节点
  if (typeof vnode === 'string' || typeof vnode === 'number') {
    return document.createTextNode(String(vnode));
  }

  // 处理 null 或 undefined
  if (vnode == null || typeof vnode === 'boolean') {
    return document.createTextNode('');
  }

  // 创建元素节点
  const { tag, props = {}, children = [] } = vnode;
  const element = document.createElement(tag);

  // 设置属性
  Object.keys(props).forEach(key => {
    setAttribute(element, key, props[key]);
  });

  // 递归处理子节点
  children.forEach(child => {
    const childElement = render(child);
    element.appendChild(childElement);
  });

  return element;
}

/**
 * 设置元素属性
 * @param {HTMLElement} element - DOM 元素
 * @param {string} key - 属性名
 * @param {any} value - 属性值
 */
function setAttribute(element, key, value) {
  // 处理 className
  if (key === 'className') {
    element.setAttribute('class', value);
    return;
  }

  // 处理 style
  if (key === 'style') {
    if (typeof value === 'string') {
      element.style.cssText = value;
    } else if (typeof value === 'object') {
      Object.keys(value).forEach(styleName => {
        element.style[styleName] = value[styleName];
      });
    }
    return;
  }

  // 处理事件监听
  if (key.startsWith('on')) {
    const eventName = key.slice(2).toLowerCase();
    element.addEventListener(eventName, value);
    return;
  }

  // 处理布尔属性
  if (typeof value === 'boolean') {
    if (value) {
      element.setAttribute(key, '');
    }
    return;
  }

  // 处理普通属性
  if (value != null) {
    element.setAttribute(key, value);
  }
}

使用示例

// 定义虚拟 DOM
const vnode = {
  tag: 'div',
  props: {
    className: 'container',
    id: 'app',
    style: {
      color: 'red',
      fontSize: '16px'
    }
  },
  children: [
    {
      tag: 'h1',
      props: {
        style: 'font-weight: bold;'
      },
      children: ['Hello World']
    },
    {
      tag: 'p',
      props: {},
      children: ['这是一段文本']
    },
    {
      tag: 'button',
      props: {
        onClick: () => alert('按钮被点击了!')
      },
      children: ['点击我']
    },
    {
      tag: 'ul',
      props: {},
      children: [
        {
          tag: 'li',
          props: {},
          children: ['列表项 1']
        },
        {
          tag: 'li',
          props: {},
          children: ['列表项 2']
        }
      ]
    }
  ]
};

// 转换为真实 DOM
const realDOM = render(vnode);

// 挂载到页面
document.body.appendChild(realDOM);

关键点

  • 递归处理:虚拟 DOM 是树形结构,需要递归遍历所有子节点并转换为真实 DOM

  • 类型判断:需要区分文本节点、元素节点、null/undefined 等不同类型的虚拟节点

  • 属性处理:不同类型的属性需要不同的处理方式

    • className 需要转换为 class 属性
    • style 可以是字符串或对象形式
    • 事件属性(以 on 开头)需要使用 addEventListener 绑定
    • 布尔属性(如 disabled)需要特殊处理
  • 文本节点创建:使用 document.createTextNode() 而不是直接设置 textContent,保证结构的一致性

  • 边界情况处理:处理 nullundefinedboolean 等特殊值,避免程序报错

  • 性能优化:在实际框架中,还需要考虑 diff 算法、批量更新等优化策略,这里只实现了基础的转换功能