虚拟dom是用javascript对象模拟真实dom结构的轻量描述,它作为真实dom和javascript逻辑之间的中间层,能够减少直接操作真实dom带来的性能损耗。在前端开发中,理解虚拟dom的实现逻辑和优势,对优化页面渲染效率有很大帮助。

javascript实现虚拟dom的核心步骤
1. 定义虚拟节点结构
首先需要定义虚拟节点的基本结构,通常包含标签名、属性、子节点、文本内容等字段,用来完整描述一个真实dom节点的信息。
// 定义虚拟节点构造函数
function VNode(tag, attrs, children, text) {
this.tag = tag; // 标签名,如div、span
this.attrs = attrs || {}; // 节点属性,如class、id、自定义属性
this.children = children || []; // 子虚拟节点数组
this.text = text || ''; // 文本内容,只有文本节点时有值
this.key = attrs ? attrs.key : undefined; // 用于diff算法的key
}
2. 创建虚拟节点的方法
为了方便创建虚拟节点,通常会封装一个创建函数,开发者可以通过调用该函数生成对应的虚拟节点对象。
// 创建虚拟节点的工具函数
function createVNode(tag, attrs, children) {
// 如果children是字符串,说明是文本节点
if (typeof children === 'string') {
return new VNode(tag, attrs, [], children);
}
// 否则处理子节点,递归创建子虚拟节点
const childVNodes = children.map(child => {
if (typeof child === 'string') {
return new VNode(undefined, {}, [], child);
}
return child;
});
return new VNode(tag, attrs, childVNodes);
}
3. 将虚拟dom转换为真实dom
有了虚拟节点之后,需要编写函数将虚拟节点渲染为真实的dom节点,同时处理节点属性和子节点的挂载。
// 将虚拟节点转换为真实dom
function renderVNode(vNode) {
// 如果是文本节点,直接创建文本节点
if (!vNode.tag) {
return document.createTextNode(vNode.text);
}
// 创建真实dom元素
const el = document.createElement(vNode.tag);
// 设置节点属性
for (let key in vNode.attrs) {
if (vNode.attrs.hasOwnProperty(key)) {
el.setAttribute(key, vNode.attrs[key]);
}
}
// 递归渲染子节点并挂载
vNode.children.forEach(child => {
el.appendChild(renderVNode(child));
});
return el;
}
4. diff算法对比更新
当数据发生变化时,会生成新的虚拟dom树,此时需要通过diff算法对比新旧两棵虚拟dom树的差异,只更新需要变动的真实dom部分,避免全量重新渲染。
// 简化的diff算法核心逻辑,对比新旧虚拟节点并更新真实dom
function diff(oldVNode, newVNode, parentEl, index = 0) {
// 如果旧节点不存在,直接新增新节点
if (!oldVNode) {
parentEl.appendChild(renderVNode(newVNode));
return;
}
// 如果新节点不存在,直接删除旧节点
if (!newVNode) {
parentEl.removeChild(parentEl.childNodes[index]);
return;
}
// 标签不同,直接替换
if (oldVNode.tag !== newVNode.tag) {
parentEl.replaceChild(renderVNode(newVNode), parentEl.childNodes[index]);
return;
}
// 标签相同,对比属性和子节点
const el = parentEl.childNodes[index];
// 对比属性
for (let key in newVNode.attrs) {
if (oldVNode.attrs[key] !== newVNode.attrs[key]) {
el.setAttribute(key, newVNode.attrs[key]);
}
}
for (let key in oldVNode.attrs) {
if (!newVNode.attrs.hasOwnProperty(key)) {
el.removeAttribute(key);
}
}
// 对比子节点,取子节点长度的最大值进行遍历
const oldChildren = oldVNode.children;
const newChildren = newVNode.children;
const maxLen = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < maxLen; i++) {
diff(oldChildren[i], newChildren[i], el, i);
}
}
虚拟dom的核心优势
1. 减少真实dom操作次数
直接操作真实dom是非常耗性能的操作,因为每次修改dom都会触发浏览器的重排和重绘。虚拟dom会先批量收集所有的修改,通过diff算法计算出最小的修改范围,最后只执行一次必要的真实dom更新,大幅减少了dom操作的次数。
2. 提升跨平台开发能力
虚拟dom本质是javascript对象,不依赖特定的运行环境。除了可以渲染为浏览器的真实dom之外,还可以渲染为原生应用的组件、服务端渲染的html字符串、小程序组件等,让同一套代码逻辑可以适配多个平台。
3. 让开发更聚焦于业务逻辑
使用虚拟dom的框架通常会提供声明式的编程方式,开发者只需要关注数据的变化,不需要手动编写复杂的dom操作代码,框架会自动处理dom的更新逻辑,降低了开发复杂度,也减少了手动操作dom带来的bug。
4. 优化批量更新场景的性能
当短时间内有多个数据变化需要更新页面时,虚拟dom可以将这些更新合并处理,避免每次数据变化都触发一次dom更新。比如在循环中多次修改数据,虚拟dom只会执行一次最终的diff和dom更新,而不是循环多少次就更新多少次。
简单示例演示
下面是一个简单的使用示例,展示如何通过虚拟dom实现页面内容的更新:
// 初始虚拟dom
const oldVNode = createVNode('div', { class: 'container' }, [
createVNode('h1', {}, '初始标题'),
createVNode('p', {}, '初始内容')
]);
// 渲染初始dom到页面
const container = document.getElementById('app');
container.appendChild(renderVNode(oldVNode));
// 数据变化后生成新的虚拟dom
const newVNode = createVNode('div', { class: 'container' }, [
createVNode('h1', {}, '更新后的标题'),
createVNode('p', {}, '更新后的内容')
]);
// 执行diff更新
diff(oldVNode, newVNode, container, 0);
上述示例中,当数据变化生成新的虚拟dom后,diff算法会对比新旧虚拟dom的差异,只更新h1和p标签的文本内容,不会重新创建整个div容器,提升了更新效率。
javascript虚拟domdiff算法前端性能优化修改时间:2026-07-23 20:33:30