虚拟 DOM 文章目录前言一、什么是虚拟 DOM1.1 定义1.2 为什么需要虚拟 DOM1.3 虚拟 DOM 的优势二、VNode 的结构2.1 基本结构2.2 VNode 的类型三、更新流程3.1 完整流程3.2 简化的实现3.3 简化的 Diff 算法四、Key 的作用与原理4.1 定义4.2 应用场景4.3 Key 用 index 的问题4.4 易混淆点五、Shadow DOM vs Virtual DOM5.1 区别5.2 Shadow DOM 示例六、虚拟 DOM 的性能6.1 虚拟 DOM 并非总是更快6.2 Vue 3 的优化七、易混淆点八、思考与练习总结前言上一篇讲了 Vue 响应式原理本篇进入虚拟 DOM——这是 React 和 Vue 等现代框架的核心机制。虚拟 DOM 解决的核心问题是如何高效地更新真实 DOM直接操作 DOM 很慢但更慢的是不必要的 DOM 操作。虚拟 DOM 通过在内存中维护一棵 JavaScript 对象树比较新旧两棵树的差异然后只更新真正变化的部分从而实现高效的 DOM 更新。本篇会讲清楚虚拟 DOM 是什么VNode 的结构是什么样的更新流程是怎样的key属性为什么重要一、什么是虚拟 DOM1.1 定义虚拟 DOM 是用 JavaScript 对象描述真实 DOM 结构的轻量级表示每次状态变化生成新的虚拟 DOM 树。// 真实 DOMdivclassboxh1Hello/h1pWorld/p/div// 虚拟 DOMVNodeconstvnode{type:div,props:{class:box},children:[{type:h1,children:Hello},{type:p,children:World}]}1.2 为什么需要虚拟 DOM问题直接操作 DOM 很慢而且很难追踪状态变化。解决方案用 JavaScript 对象描述 DOM 结构VNode状态变化时生成新的 VNode 树比较新旧两棵树的差异Diff只更新真正变化的部分到真实 DOM1.3 虚拟 DOM 的优势声明式 UI开发者只需描述目标状态框架自动处理 DOM 更新批量更新将多次状态变更合并为一次 DOM 更新跨平台同一套 VNode 树可渲染到浏览器 DOM、SSR HTML、Canvas 或移动端原生视图可预测性状态 → 视图的映射是确定性的便于调试和测试二、VNode 的结构2.1 基本结构// 简化的 VNode 结构constvnode{type:div,// 标签名或组件props:{// 属性class:box,onClick:handler},children:[// 子节点{type:h1,children:Hello},{type:p,children:World}],key:unique-id,// 唯一标识可选el:null// 真实 DOM 元素引用运行时填充}2.2 VNode 的类型// 文本节点{type:null,children:Hello World}// 元素节点{type:div,props:{},children:[]}// 组件节点{type:MyComponent,props:{msg:hello}}// FragmentVue 3 支持多根节点{type:Fragment,children:[...]}三、更新流程3.1 完整流程1. 状态变化响应式触发 2. 生成新的 VNode 树 3. 与旧 VNode 树进行 Diff 比较 4. 计算出最小变更集 5. 批量应用到真实 DOM3.2 简化的实现// 创建 VNodeconsth(type,props,...children)({type,props:props??{},children:children.flat()})// 渲染 VNode 到真实 DOMconstrender(vnode,container){consteldocument.createElement(vnode.type)// 设置属性Object.entries(vnode.props).forEach(([key,value]){if(key.startsWith(on)){el.addEventListener(key.slice(2).toLowerCase(),value)}else{el.setAttribute(key,value)}})// 递归渲染子节点vnode.children.forEach(child{if(typeofchildstring){el.appendChild(document.createTextNode(child))}else{render(child,el)}})container.appendChild(el)}// 使用constapph(div,{class:app},h(h1,null,Hello),h(p,null,World))render(app,document.getElementById(root))3.3 简化的 Diff 算法// 比较新旧 VNodeconstdiff(oldVNode,newVNode){// 节点类型不同直接替换if(oldVNode.type!newVNode.type){return{type:REPLACE,newVNode}}// 文本节点内容变化if(typeofnewVNode.childrenstringoldVNode.children!newVNode.children){return{type:TEXT,text:newVNode.children}}// 属性变化constpropPatchesdiffProps(oldVNode.props,newVNode.props)// 子节点变化constchildPatchesdiffChildren(oldVNode.children,newVNode.children)return{type:UPDATE,propPatches,childPatches}}四、Key 的作用与原理4.1 定义key是 Vue 在v-for列表渲染中用于标识每个节点的唯一属性帮助 Diff 算法高效匹配新旧节点。Vue 的 Diff 算法通过key判断节点是否可以复用相同 key 的节点进行 patch 比较不同 key 则销毁重建。不推荐使用index作为 key因为列表增删会导致索引重排引起不必要的 DOM 重建和状态丢失。key不仅用于v-for在切换动态组件或触发过渡动画时也需要 key 来区分不同元素。4.2 应用场景列表渲染中使用唯一 ID如item.id作为 key保证列表增删时最小化 DOM 操作。强制重新渲染组件给同一组件切换不同的 key 值Vue 会销毁旧实例并创建新实例。表格行数据更新时使用后端返回的业务 ID 作为 key避免用户输入框内容错位。4.3 Key 用 index 的问题// ❌ 错误使用 index 作为 keyli v-for(item, index) in list:keyindexinput v-modelitem.name//li// 问题在列表头部插入新项时// 旧: [A, B, C] → 新: [D, A, B, C]// key0: A → D (复用 A 的 DOM但内容变成 D)// key1: B → A (复用 B 的 DOM但内容变成 A)// key2: C → B (复用 C 的 DOM但内容变成 B)// key3: 新增 C// 结果所有输入框的内容错位// ✅ 正确使用唯一 ID 作为 keyli v-foritem in list:keyitem.idinput v-modelitem.name//li// 结果只有新增的 D 需要创建 DOM其他节点正确复用4.4 易混淆点使用index作 key 在列表只有追加操作时表现正常但有插入/删除/排序时会出现性能问题和状态错乱。key 必须是唯一且稳定的值使用Math.random()作为 key 会导致每次渲染都重建所有节点。key的作用不仅是优化性能更重要的是保证组件状态的正确性和 DOM 复用的准确性。Vue 3 的v-for中key是必须的会发出警告Vue 2 中则是可选的。五、Shadow DOM vs Virtual DOM5.1 区别对比项Shadow DOMVirtual DOM定义浏览器原生组件封装技术Web Components框架层的抽象目的封装组件内部结构和样式高效更新 DOM实现浏览器原生支持JavaScript 对象树使用Web Components 标准React / Vue 等框架5.2 Shadow DOM 示例// Web Components 使用 Shadow DOMclassMyCardextendsHTMLElement{constructor(){super()constshadowthis.attachShadow({mode:open})shadow.innerHTMLstyle .card { border: 1px solid #ccc; padding: 16px; } /style div classcard slot/slot /div}}customElements.define(my-card,MyCard)// 使用// my-cardpHello/p/my-card关键区别Shadow DOM 是浏览器原生的组件封装技术Virtual DOM 是框架层的抽象两者完全无关。六、虚拟 DOM 的性能6.1 虚拟 DOM 并非总是更快虚拟 DOM 的核心优势不在于比直接操作 DOM 快而在于声明式开发开发者只需描述目标状态框架自动计算最优更新批量更新将多次状态变更合并为一次 DOM 更新可预测性状态 → 视图的映射是确定性的在极少量的 DOM 更新场景下手动 DOM 操作可能更快。虚拟 DOM 的价值在于复杂场景下的批量更新和跨平台抽象。6.2 Vue 3 的优化Vue 3 的快速 Diff 算法借鉴了 Inferno通过预处理最长递增子序列LIS优化移动操作// Vue 3 的编译时优化// 1. 静态提升静态节点只创建一次// 2. Patch Flag标记动态内容类型// 3. Block Tree只比较动态节点七、易混淆点虚拟 DOM ≠ 更快对于简单场景如修改一个文本直接操作 DOM 可能更快VNode 的优势在于复杂场景下的批量更新和跨平台抽象。Shadow DOM ≠ Virtual DOMShadow DOM 是浏览器原生组件封装技术Web ComponentsVirtual DOM 是框架层的抽象两者无关。Key 的作用Diff 算法通过 key 判断节点是否可复用key 不稳定如用 index会导致错误的节点复用和不必要的 DOM 操作。Vue 3 的优化通过编译时优化静态提升、Patch Flag、Block Tree减少需要 Diff 的节点数。八、思考与练习1.为什么 Vue 3 强制要求v-for必须有key解析key帮助 Diff 算法高效识别节点的移动、新增和删除。没有 key 时Vue 只能按顺序比较可能导致错误的节点复用和状态丢失。2.使用index作为 key 在什么场景下会出问题解析列表有插入、删除、排序操作时会出问题。例如在头部插入新项所有节点的 index 都会变化导致 DOM 复用错误。3.虚拟 DOM 的核心价值是什么解析声明式开发 批量更新 跨平台。开发者只需描述目标状态框架自动计算最优更新策略。4.Vue 3 的编译时优化有哪些解析静态提升静态节点只创建一次后续复用Patch Flag标记动态内容类型文本、属性等跳过静态内容Block Tree只比较动态节点减少 Diff 范围5.Shadow DOM 和 Virtual DOM 有什么关系解析没有关系。Shadow DOM 是浏览器原生的组件封装技术Web ComponentsVirtual DOM 是框架层的抽象两者目的和实现完全不同。6.如何强制重新渲染一个组件解析给组件绑定不同的key值Vue 会销毁旧实例并创建新实例Component :keyuniqueId /总结虚拟 DOM是用 JavaScript 对象描述真实 DOM 结构的轻量级抽象VNode是虚拟 DOM 的基本单位包含类型、属性、子节点等信息更新流程状态变化 → 生成新 VNode → Diff 比较 → 最小变更 → 更新 DOMKey 的作用帮助 Diff 算法高效识别节点保证状态正确性虚拟 DOM 的价值声明式开发、批量更新、跨平台而非比直接操作 DOM 快