Vue的虚拟DOM

本文介绍了DOM和虚拟DOM(vdom)的解析流程,对比了虚拟DOM与传统DOM,指出虚拟DOM可避免无用计算。还阐述了diff算法,数据改变时订阅者用patch更新视图,该算法只比较同层次节点,通过指针移动处理节点,层层递归完成子节点比对。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DOM的解析流程分为五步
创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting

  • 处理HTML标签建立DOM树
  • 处理CSS标签建立CSSOM树
  • 连接CSSOM树和DOM树形成一个render树
  • 在render树上运行布局来计算每个节点的形状
  • 在屏幕上画每一个节点

vdom的解析流程

  • 用JavaScript对象模拟DOM
  • 把此虚拟DOM转成真实DOM并插入页面中
  • 如果有事件发生修改了虚拟DOM
  • 比较两棵虚拟DOM树的差异,得到差异对象
  • 把差异对象应用到真正的DOM树上

虚拟DOM与传统DOM相比的优势

对于传统DOM,如果一个操作需要修改5次DOM,那么浏览器就会依次完成5次修改,修改DOM的代价很大,这样会做很多无用操作,浪费性能。
而对于虚拟DOM,同样的一个操作,并不会马上修改真实的DOM,而是将每一次修改生成的虚拟DOM进行diff比较并将不同的地方保存在JS对象中(虚拟DOM),在5次操作结束后再一起映射到真实DOM中去,从而避免无用的计算。

关于diff算法

当数据发生改变时,set方法会让调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,更新相应的视图。

  • diff算法只会对同层次节点进行同比较,这是为了降低算法的复杂度,提高效率。
  • 首先判断两个节点是否值得比较(不看子节点是否相同),如果节点不同则直接替换,如果相同则用patch经行比较
patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        api.setTextContent(el, vnode.text)
    }else {
        updateEle(el, vnode, oldVnode)
        if (oldCh && ch && oldCh !== ch) {
            updateChildren(el, oldCh, ch)
        }else if (ch){
            createEle(vnode) //create el's children dom
        }else if (oldCh){
            api.removeChildren(el)
        }
    }
}

找到对应的真实dom,称为el
判断Vnode和oldVnode是否指向同一个对象,如果是,那么直接return
如果他们都有文本节点并且不相等,那么将el的文本节点设置为Vnode的文本节点。
如果oldVnode有子节点而Vnode没有,则删除el的子节点
如果oldVnode没有子节点而Vnode有,则将Vnode的子节点真实化之后添加到el
如果两者都有子节点,则执行updateChildren函数比较子节点,这一步很重要

下面是updateChildren过程

  • Vue中diff实现了oldStart+oldEnd,newStart+newEnd俩对指针,分别对应oldVdom和newVdom的起点和终点。起止点之前的节点是待处理的节点,Vue不断对vnode进行处理同时移动指针直到其中任意一对起点和终点相遇。处理过的节点Vue会在oldVdom和newVdom中同时将它标记为已处理。

  • 逐个遍历newVdom的节点,找到它在oldVdom中的位置,如果找到了就移动对应的DOM元素,如果没找到说明是新增节点,则新建一个节点插入。遍历完成之后如果oldVdom中还有没处理过的节点,则说明这些节点在newVdom中被删除了,删除它们即可。如果newVdom中还有没处理的节点,就说明新增了节点。

  • 这样层层递归下去,直到将oldVnode和Vnode中的所有子节点比对完。

### Vue.js 中虚拟 DOM 的工作原理 #### 1. 虚拟 DOM 是什么? 虚拟 DOM(Virtual DOM)是一个轻量级的 JavaScript 对象,它是真实 DOM 的抽象表示。它并不是浏览器中的实际 DOM 结构,而是一组用于描述页面结构的数据对象树。通过操作虚拟 DOM 来代替直接操作真实的 DOM,可以显著减少昂贵的 DOM 操作开销[^3]。 #### 2. 虚拟 DOM 的创建过程 当组件的状态发生变化时,Vue 会重新渲染整个组件并生成一个新的虚拟 DOM 树。这个新的虚拟 DOM 树会被与之前的虚拟 DOM 树进行比较,从而计算出最小化的差异集合。这种比较的过程被称为 **Diff 算法**[^4]。 以下是虚拟 DOM 创建的主要流程: - 组件状态变化触发 `render` 函数执行。 - `render` 函数返回一个代表 UI 层次结构的对象——即虚拟 DOM。 - 这些对象包含了标签名、属性以及子节点等信息。 ```javascript // 示例:简单的 render 函数返回虚拟 DOM function render() { return { tag: 'div', attrs: {}, children: ['Hello, Virtual DOM!'] }; } ``` #### 3. Diff 算法的核心机制 为了高效地找到两棵虚拟 DOM 树之间的差异,Vue 使用了一种优化过的 diff 算法。该算法基于以下几个假设来降低复杂度: - **同层比较**:只在同一层次上比较节点,不跨层级寻找可能的复用节点。 - **key 属性的作用**:如果存在 key,则认为具有相同 key 的节点可能是相同的节点;如果没有 key,则按照位置索引来判断是否为同一节点[^2]。 具体来说,diff 算法分为三个阶段: 1. **Same Node Check (相同节点校验)** 如果两个节点的类型一致且 key 相同,则进入下一步更新逻辑;否则直接移除旧节点并插入新节点。 2. **Attributes Diffing (属性差分)** 针对保留下来的节点,进一步检测其属性是否有变动,并同步修改至真实 DOM 上。 3. **Children Recursing (递归处理子节点)** 对于拥有子节点的情况,继续调用 diff 方法逐层深入直至叶子节点完成全部比对。 #### 4. 性能优化策略 尽管虚拟 DOM 提供了高效的更新方案,但在某些场景下仍需注意潜在瓶颈。例如频繁的大规模列表重绘可能会抵消掉部分收益。因此,在开发过程中推荐遵循以下建议以充分利用虚拟 DOM 的优势: - 尽量给动态列表项设置唯一的 `key` 值以便更精确地标记每个元素的身份; - 利用 shouldComponentUpdate 或者 computed 属性控制不必要的重新渲染行为[^1]。 ```javascript // 正确使用 key 的例子 <template> <ul> <li v-for="item in items" :key="item.id">{{ item.name }}</li> </ul> </template> <script> export default { data() { return { items: [{ id: 1, name: 'Item A' }, { id: 2, name: 'Item B' }] } } }; </script> ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值