目录
一、虚拟 DOM 的核心概念
二、虚拟 DOM 到真实 DOM 的流程
三、手写虚拟 DOM 到真实 DOM 的实现
1. 定义虚拟 DOM 的结构(VNode)
2. 创建虚拟 DOM 转真实 DOM 的函数
3. 挂载虚拟 DOM 到页面
4. 更新虚拟 DOM 的过程(Diff 算法简化版)
四、完整示例:虚拟 DOM 到真实 DOM 的生命周期
五、总结
一、虚拟 DOM 的核心概念
虚拟 DOM 是用 JavaScript 对象(VNode)模拟真实 DOM 结构的轻量级抽象。它的核心作用是:
- 减少直接操作真实 DOM 的次数:通过对比新旧虚拟 DOM 树的差异(Diff 算法),仅更新变化的部分。
- 声明式编程:开发者只需关注数据逻辑,无需手动操作 DOM。
- 跨平台能力:虚拟 DOM 可用于 Web、移动端(如 Weex)或服务端渲染(SSR)
二、虚拟 DOM 到真实 DOM 的流程
Vue 的渲染流程可分为以下步骤:
- 模板编译:将模板(
<template>
)编译为渲染函数(render function
)。 - 生成虚拟 DOM 树:通过
render
函数返回一个虚拟 DOM 树(由 VNode 节点组成)。 - 首次挂载:将虚拟 DOM 树转化为真实 DOM 并渲染到页面。
- 数据更新:生成新的虚拟 DOM 树,与旧树进行 Diff 算法比较。
- 局部更新:将差异部分应用到真实 DOM 上(Patch 过程)。
三、手写虚拟 DOM 到真实 DOM 的实现
以下是一个简化版的实现示例,涵盖虚拟 DOM 的创建、挂载和更新过程。
1. 定义虚拟 DOM 的结构(VNode)
// VNode 结构
const vnode = {tag: 'div', // 标签名props: { id: 'app' }, // 属性children: [ // 子节点{tag: 'p',props: { class: 'text' },children: ['Hello, Vue!']}]
};
2. 创建虚拟 DOM 转真实 DOM 的函数
// 将虚拟 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 到页面
// 将虚拟 DOM 挂载到容器
function render(vnode, container) {const dom = createDom(vnode); // 生成真实 DOMcontainer.appendChild(dom); // 挂载到页面
}// 示例:将虚拟 DOM 挂载到 #root 容器
const root = document.getElementById('root');
render(vnode, root);
4. 更新虚拟 DOM 的过程(Diff 算法简化版)
// 比较新旧虚拟 DOM 树并更新真实 DOM
function patch(oldVnode, newVnode, parentDom) {// 如果节点类型不同,直接替换if (oldVnode.tag !== newVnode.tag) {parentDom.replaceChild(createDom(newVnode), oldVnode.elm);return;}// 获取真实 DOMconst 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 的生命周期
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>手写 Vue 虚拟 DOM</title>
</head>
<body><div id="root"></div><script>// 1. 初始虚拟 DOMconst initialVnode = {tag: 'div',props: { id: 'app' },children: [{tag: 'p',props: { class: 'text' },children: ['Hello, Vue!']}]};// 2. 挂载初始虚拟 DOMconst root = document.getElementById('root');const initialDom = createDom(initialVnode);root.appendChild(initialDom);// 3. 模拟数据更新后的新虚拟 DOMconst newVnode = {tag: 'div',props: { id: 'app' },children: [{tag: 'p',props: { class: 'text updated' },children: ['Hello, Vue! Updated!']}]};// 4. 更新虚拟 DOM 到真实 DOMpatch(initialVnode, newVnode, root);</script>
</body>
</html>
五、总结
通过上述实现,我们可以看到 Vue 虚拟 DOM 的核心原理:
- 虚拟 DOM 是 JavaScript 对象:通过
createDom
函数将虚拟 DOM 转化为真实 DOM。 - Diff 算法是性能优化的关键:通过比较新旧虚拟 DOM 树的差异,仅更新变化的部分。
- 局部更新减少重排/重绘成本:避免了直接操作真实 DOM 的高昂代价。
在实际开发中,Vue 的虚拟 DOM 实现更为复杂(如处理组件、事件绑定等),但核心思想一致。掌握虚拟 DOM 的原理,不仅能帮助我们更好地理解 Vue 的运行机制,还能在性能优化和跨平台开发中游刃有余。