Vue 虚拟DOM和DIff算法
一. 什么是虚拟DOM?
虚拟DOM(Virtual DOM), 本质上是用js对象来描述DOM结构的树形数据,然后再通过特定的render方法将其渲染成真是的DOM
它的主要作用是:
- 减少直接操作真实DOM带来的性能开销
- 通过比较新旧虚拟DOM差异,只对变化的部分进行最小化更新
通俗理解:
虚拟DOM就是"用对象模拟HTML结构",每次状态变更先修改对象,最后再批量同步到页面上
二.为什么需要虚拟DOM ?
- 减少DOM操作频率; 浏览器DOM操作是昂贵的(重排,重绘),虚拟DOM允许我们"批量修改",
- 跨平台: 虚拟DOM并不依赖浏览器DOM,本质是纯对象,可用于SSR,原生渲染等
- 提高渲染性能: 通过diff算法,可以最小化更新真实DOM,提升性能
三.虚拟DOM与真实DOM的区别
项目 | 虚拟 DOM(VNode) | 真实 DOM(DOM Element) |
---|---|---|
本质 | JS 对象(树) | 浏览器提供的 HTML 节点(树) |
创建开销 | 非常小,内存中构建 | 较大,操作会触发浏览器重排和重绘 |
渲染位置 | 内存中 | 显示在网页上 |
修改方式 | 对象属性直接修改 | 需要调用原生 DOM API |
依赖平台 | 与平台无关(跨平台) | 依赖浏览器 DOM API |
1.虚拟dom
const vnode = {tag: 'div',data: { id: 'app' },children: [{ tag: 'p', data: {}, children: [], text: 'Hello' },{ tag: 'button', data: {}, children: [], text: 'Click me' }],text: undefined,elm: undefined
}
- tag: 标签名
- data: 表示该标签的属性(如id)
- children 表示子元素
- text 表示文本内容
- elm 用于存储实际的DOM元素引用
2.真实的dom
<div id="app"><p>Hello</p><button>Click me</button>
</div>
四.Diff 算法 : 虚拟DOM更新的核心
1.为什么需要Diff算法?
每当数据发生变化时,Vue会重新渲染组件并生成一个新的虚拟DOM树,然后,Vue会比较新旧虚拟DOM树之间的差异,计算出最小化的DOM更新(这个过程叫做DIff),这样做的目的是以避免频繁地操真实DOM,减少重排和重绘,提升性能
DIff的核心思想
Vue的DIff算法通过一下几个核心思想来高效比较新旧Vnode树:
- 同层比较: Vue会在同一层级上进行比较,而不会跨层比较,这意味着Vue会首先比较根节点,接着比较每个子节点,
- 双端比较: Vue的Diff算法通过从两端开始对比(即从头和尾同时对比)来最大化复用节点,这样可以减少不必要的比较
- 复用节点: 只有当新旧节点是同类型(sameVnode)是,Vue才会尝试复用旧的DOM元素,否则就会销毁旧节点并创建新节点
- 最小化更新: 通过计算出差异后,Vue只更新那些发生变化的部分,而不是重新渲染整个DOM树
同层比较与双端比较
Vue的Diff算法先比较两个虚拟DOM树的根节点,然后从根节点触发,依次比较左右子节点,采用"双端对比"的策略,即:
1. 从头开始比较: 比较oldStart和newStart;
2. 从尾开始比较: 比较oldEnd和newEnd;
3. 尝试优化节点的复用,最小化DOM操作.
具体流程
- 更新触发: 当响应式数据发生变化时,Vue会触发相应的Watcher
- 重新渲染: Watcher会触发render( ),生成新的虚拟DOM(VNode)
- Diff比较: Vue将新的VNode和旧的Vnode进行比对,使用DIff算法计算最小的变化;
- Patch更新: 计算出差异后,Vue会通过patch操作将差异应用到真实DOM上
2.同一个虚拟节点(sameVnde)判断
function sameVnode(a,b){return a.key===b.key && a.tag===b.tag
}
- key: 当Vnode的key一样是,表示它们是同一类型的节点,Vue可以复用这个节点
- tag: tag相同表示它们是相同类型的元素(如div,p,button等)
3.Vnode和真实DOM更新过程Patch函数
Vue在执行更新时,会通过patch函数来进行DOm的更新,patch函数的核心就是对比新旧虚拟DOM节点的差异,然后通过最小化更新来优化DOM操作
function patch(oldVnode, vnode) {if (!sameVnode(oldVnode, vnode)) {const parent = oldVnode.elm.parentNode;const newElm = createElm(vnode);parent.insertBefore(newElm, oldVnode.elm);parent.removeChild(oldVnode.elm);} else {patchVnode(oldVnode, vnode);}
}
- sameVnode: 首先判断是否是同一个节点
- patchVnode: 如果是同一个节点,则执行节点的属性,文本,子节点等的更新
- createElm: 如果节点类型不同,Vue会销毁旧节点并创建新节点
patchVnode处理节点更新
1. 更新文本: 如果新旧节点都是文本节点,只需要更新文本内容
2.更新子节点: 如果有子节点,Vue会调用updateChildren来执行子节点的更新
3.更新属性: 如果节点的属性(如class,style等)有变化,Vue会通过原生DOMAPI更新这些属性
五. 总结
Vue通过虚拟DOM和Diff算法,极大地优化了性能,当数据发生变化时,Vue会生成新的虚拟DOM,通过DIff算法与旧的虚拟DOM树对比,最终只更新有变化的部分,从而减少了直接操作真实DOM的次数,提升了页面的响应速度,结合合理的性能优化策略,可以让Vue在大规模应用中也能保持高效的性能