目录
[一、虚拟 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 结构的轻量级抽象。它的核心作用是:
- 减少直接操作真实 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)
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 的核心原理:
- 虚拟 DOM 是 JavaScript 对象 :通过
createDom
函数将虚拟 DOM 转化为真实 DOM。 - Diff 算法是性能优化的关键:通过比较新旧虚拟 DOM 树的差异,仅更新变化的部分。
- 局部更新减少重排/重绘成本:避免了直接操作真实 DOM 的高昂代价。
在实际开发中,Vue 的虚拟 DOM 实现更为复杂(如处理组件、事件绑定等),但核心思想一致。掌握虚拟 DOM 的原理,不仅能帮助我们更好地理解 Vue 的运行机制,还能在性能优化和跨平台开发中游刃有余。