手写 Vue 中虚拟 DOM 到真实 DOM 的完整过程

目录

[一、虚拟 DOM 的核心概念](#一、虚拟 DOM 的核心概念)

[二、虚拟 DOM 到真实 DOM 的流程](#二、虚拟 DOM 到真实 DOM 的流程)

[三、手写虚拟 DOM 到真实 DOM 的实现](#三、手写虚拟 DOM 到真实 DOM 的实现)

[1. 定义虚拟 DOM 的结构(VNode)](#1. 定义虚拟 DOM 的结构(VNode))

[2. 创建虚拟 DOM 转真实 DOM 的函数](#2. 创建虚拟 DOM 转真实 DOM 的函数)

[3. 挂载虚拟 DOM 到页面](#3. 挂载虚拟 DOM 到页面)

[4. 更新虚拟 DOM 的过程(Diff 算法简化版)](#4. 更新虚拟 DOM 的过程(Diff 算法简化版))

[四、完整示例:虚拟 DOM 到真实 DOM 的生命周期](#四、完整示例:虚拟 DOM 到真实 DOM 的生命周期)

五、总结


一、虚拟 DOM 的核心概念

虚拟 DOM 是用 JavaScript 对象(VNode)模拟真实 DOM 结构的轻量级抽象。它的核心作用是:

  1. 减少直接操作真实 DOM 的次数:通过对比新旧虚拟 DOM 树的差异(Diff 算法),仅更新变化的部分。
  2. 声明式编程:开发者只需关注数据逻辑,无需手动操作 DOM。
  3. 跨平台能力:虚拟 DOM 可用于 Web、移动端(如 Weex)或服务端渲染(SSR)

二、虚拟 DOM 到真实 DOM 的流程

Vue 的渲染流程可分为以下步骤:

  1. 模板编译 :将模板(<template>)编译为渲染函数(render function)。
  2. 生成虚拟 DOM 树 :通过 render 函数返回一个虚拟 DOM 树(由 VNode 节点组成)。
  3. 首次挂载:将虚拟 DOM 树转化为真实 DOM 并渲染到页面。
  4. 数据更新:生成新的虚拟 DOM 树,与旧树进行 Diff 算法比较。
  5. 局部更新:将差异部分应用到真实 DOM 上(Patch 过程)。

三、手写虚拟 DOM 到真实 DOM 的实现

以下是一个简化版的实现示例,涵盖虚拟 DOM 的创建、挂载和更新过程。

1. 定义虚拟 DOM 的结构(VNode)
javascript 复制代码
// VNode 结构
const vnode = {
  tag: 'div',          // 标签名
  props: { id: 'app' }, // 属性
  children: [          // 子节点
    {
      tag: 'p',
      props: { class: 'text' },
      children: ['Hello, Vue!']
    }
  ]
};
2. 创建虚拟 DOM 转真实 DOM 的函数
javascript 复制代码
// 将虚拟 DOM 转换为真实 DOM
function createDom(vnode) {
  const { tag, props, children } = vnode;
  const dom = document.createElement(tag); // 创建真实 DOM 元素

  // 设置属性
  if (props && typeof props === 'object') {
    updateProps(dom, props);
  }

  // 处理子节点
  if (Array.isArray(children)) {
    reconcileChildren(children, dom);
  }

  return dom;
}

// 设置属性
function updateProps(dom, props) {
  for (const key in props) {
    if (key === 'style') {
      // 处理 style 属性
      for (const styleKey in props.style) {
        dom.style[styleKey] = props.style[styleKey];
      }
    } else {
      // 处理其他属性(id、class 等)
      dom[key] = props[key];
    }
  }
}

// 递归处理子节点
function reconcileChildren(children, dom) {
  for (const child of children) {
    const childDom = createDom(child); // 递归创建子节点
    dom.appendChild(childDom);
  }
}
3. 挂载虚拟 DOM 到页面
javascript 复制代码
// 将虚拟 DOM 挂载到容器
function render(vnode, container) {
  const dom = createDom(vnode); // 生成真实 DOM
  container.appendChild(dom);   // 挂载到页面
}

// 示例:将虚拟 DOM 挂载到 #root 容器
const root = document.getElementById('root');
render(vnode, root);
4. 更新虚拟 DOM 的过程(Diff 算法简化版)
javascript 复制代码
// 比较新旧虚拟 DOM 树并更新真实 DOM
function patch(oldVnode, newVnode, parentDom) {
  // 如果节点类型不同,直接替换
  if (oldVnode.tag !== newVnode.tag) {
    parentDom.replaceChild(createDom(newVnode), oldVnode.elm);
    return;
  }

  // 获取真实 DOM
  const elm = (newVnode.elm = oldVnode.elm);

  // 更新属性
  updateProps(elm, oldVnode.props, newVnode.props);

  // 递归比较子节点
  patchChildren(oldVnode.children, newVnode.children, elm);
}

// 更新属性
function updateProps(dom, oldProps, newProps) {
  // 移除旧属性
  for (const key in oldProps) {
    if (!newProps[key]) {
      dom[key] = null;
    }
  }

  // 更新或新增属性
  for (const key in newProps) {
    if (key === 'style') {
      for (const styleKey in newProps.style) {
        dom.style[styleKey] = newProps.style[styleKey];
      }
    } else {
      dom[key] = newProps[key];
    }
  }
}

// 递归比较子节点
function patchChildren(oldChildren, newChildren, parentDom) {
  // 简化版:逐个比较子节点
  const maxLength = Math.max(oldChildren.length, newChildren.length);
  for (let i = 0; i < maxLength; i++) {
    const oldChild = oldChildren[i];
    const newChild = newChildren[i];
    if (oldChild && newChild) {
      patch(oldChild, newChild, parentDom);
    } else if (newChild) {
      // 新增节点
      parentDom.appendChild(createDom(newChild));
    } else if (oldChild) {
      // 删除节点
      parentDom.removeChild(oldChild.elm);
    }
  }
}

四、完整示例:虚拟 DOM 到真实 DOM 的生命周期

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>手写 Vue 虚拟 DOM</title>
</head>
<body>
  <div id="root"></div>

  <script>
    // 1. 初始虚拟 DOM
    const initialVnode = {
      tag: 'div',
      props: { id: 'app' },
      children: [
        {
          tag: 'p',
          props: { class: 'text' },
          children: ['Hello, Vue!']
        }
      ]
    };

    // 2. 挂载初始虚拟 DOM
    const root = document.getElementById('root');
    const initialDom = createDom(initialVnode);
    root.appendChild(initialDom);

    // 3. 模拟数据更新后的新虚拟 DOM
    const newVnode = {
      tag: 'div',
      props: { id: 'app' },
      children: [
        {
          tag: 'p',
          props: { class: 'text updated' },
          children: ['Hello, Vue! Updated!']
        }
      ]
    };

    // 4. 更新虚拟 DOM 到真实 DOM
    patch(initialVnode, newVnode, root);
  </script>
</body>
</html>

五、总结

通过上述实现,我们可以看到 Vue 虚拟 DOM 的核心原理:

  1. 虚拟 DOM 是 JavaScript 对象 :通过 createDom 函数将虚拟 DOM 转化为真实 DOM。
  2. Diff 算法是性能优化的关键:通过比较新旧虚拟 DOM 树的差异,仅更新变化的部分。
  3. 局部更新减少重排/重绘成本:避免了直接操作真实 DOM 的高昂代价。

在实际开发中,Vue 的虚拟 DOM 实现更为复杂(如处理组件、事件绑定等),但核心思想一致。掌握虚拟 DOM 的原理,不仅能帮助我们更好地理解 Vue 的运行机制,还能在性能优化和跨平台开发中游刃有余。

相关推荐
灿灿1213813 分钟前
CSS 文字浮雕效果:巧用 text-shadow 实现 3D 立体文字
前端·css
Jay_51514 分钟前
C++多态与虚函数详解:从入门到精通
开发语言·c++
路来了15 分钟前
Python小工具之PDF合并
开发语言·windows·python
烛阴30 分钟前
Babel 完全上手指南:从零开始解锁现代 JavaScript 开发的超能力!
前端·javascript
AntBlack1 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669131 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
尘心cx1 小时前
前端-CSS-day1
前端·css
知否技术1 小时前
前端常说的 SCSS是个啥玩意?一篇文章给你讲的明明白白!
前端·scss
CN-Dust1 小时前
[FMZ][JS]第一个回测程序--让时间轴跑起来
javascript
xiaolang_8616_wjl1 小时前
c++文字游戏_闯关打怪
开发语言·数据结构·c++·算法·c++20