Vue 虚拟dom 和 diff 算法

一. 什么是虚拟dom

本质上就是一个 JS 对象,作用:本质是保存节点信息, 属性和内容的一个JS对象。

比如 template 里标签结构

对应的虚拟 DOM 结构

两个虚拟dom 之间 的对比    与  两个真实的dom 的对比 要简单多

虚拟dom:和真实的dom 相比 只会抽出个别属性

二. vue 是如何利用虚拟dom来更新

  • 初次渲染时,会根据 model 数据创建一个虚拟 DOM 对象(树)

  • 根据虚拟 DOM 生成真正的 DOM,渲染到页面 
  • 当数据变化后,会重新根据新的数据,创建新的虚拟 DOM 对象(树)

此时有两份虚拟 dom,利用 diff 算法进行两份虚拟 dom 进行对比

Vue 渲染真实dom的原则 一般都是采用就地复用,只更新 改变了的真实dom

为什么使用虚拟dom

虚拟dom可以进行高效更新(体现在批量处理dom等),同时也可以使用虚拟dom进行跨平台: 开发的代码只是模板代码 => 虚拟dom => web(编译为web的dom) => (小程序的节点)

三. Diff的流程

Vue中,主要是patch()patchVnode()updateChildren()这三个主要方法来实现Diff的。

  1. 当我们Vue中的响应式数据变化的时候,就会触发页面更新函数updateComponent()
  2. 此时updateComponet()就会调用patch()方法,在该方法中进行比较是否为相同节点,是的话执行patchVnode()方法,开始比较节点差异;而如果不是相同节点的话,则进行替换操作,具体后面会讲到; 
  3. patchVnode()中,首先是更新节点属性,然后会判断有没有孩子节点,有的话则执行updateChildren()方法,对孩子节点进行比较;如果没有孩子节点的话,则进行节点文本内容判断更新;(文本节点是不会有孩子节点的) 
  4. updateChildren()中,会对传入的两个孩子节点数组进行一一比较,当找到相同节点的情况下,调用patchVnode()继续节点差异比较。

3.1 SameVnode

在源码中会用sameVnode()方法去判断两个节点是否相同,实质上是通过去判断key值,tag标签等静态属性从而去判断两个节点是否为相同节点。

注意的是,这里的相同节点不意味着为相等节点,比如<div>HelloWorld</div><div>HiWorld</div>为相同节点,但是它们并不相等。在源码中是通过vnode1 === vnode2去判断是不是为相等节点。

3.2 Patch

patch()方法,该方法接收新旧虚拟Dom,即oldVnodevnode,这个函数其实是对新旧虚拟Dom做一个简单的判断,而还没有进入详细的比较阶段。

  • 首先判断vnode是否存在,如果不存在的话,则代表这个旧节点要整个删除; 
  • 如果vnode存在的话,再判断oldVnode是否存在,如果不存在的话,则代表只需要新增整个vnode节点就可以; 
  • 如果两者不是相同节点的话,vnode直接替换 oldVnode
  • 如果vnodeoldVnode都存在的话(sameVnode),判断两者是不是相同节点,如果是的话,这调用patchVnode方法,对两个节点进行详细比较判断;

3.3 PatchVnode

patchVnode()中,同样是接收新旧虚拟Dom,即oldVnodevnode;在该函数中,即开始对两个虚拟Dom进行比较更新了。

  • 首先判断两个虚拟Dom是不是全等,即没有任何变动;是的话直接结束函数,否则继续执行;
  • 其次更新节点的属性;
  • 接着判断vnode.text是否存在,即vnode是不是文本节点。是的话,只需要更新节点文本既可,否则的话,这继续比较;
  • 判断vnodeoldVnode是否有孩子节点: 
    • 如果两者都有孩子节点的话,执行updateChildren()方法,进行比较更新孩子节点;
    • 如果vnode有孩子节点而oldVnode没有的话,则直接新增所有孩子节点,并将该节点文本属性设为空;
    • 如果oldVnode有孩子节点而vnode没有的话,则直接删除所有孩子节点;
    • 如果两者都没有孩子节点,就判断oldVnode.text是否有内容,有的话清空内容既可

3.4 UpdateChildren

  • 这个方法传入三个比较重要的参数,即parentElm父级真实节点,便于直接节点操作;oldCholdVnode的孩子节点;newChVnode的孩子节点。

OldChnewCh都是一个数组。 这个方法的作用,就是对这两个数组一一比较,找到相同的节点,执行patchVnode再次进行比较更新,剩下的少退多补。

我们想到最简单的方法,就是两个数组进行遍历匹配,但是这样子的复杂度是很大的,而且我们真实项目中,页面结构是非常庞大和复杂的,所以这个方案是非常耗性能的。

diff 算法 其实就是为了提高新旧虚拟dom节点的 对比效率的, 采取一种算法

Vue2 里采用的是 双针比较法

  • 首先是oldStartVnodenewStartVnode进行比较,如果比较相同的话,移动oldStartIdxnewStartIdx

  • 如果oldStartVnodenewStartVnode匹配不上的话,接下来就是oldEndVnodenewEndVnode做比较了。
  • 但如果两头比较和两尾比较都不是相同节点的话,这时候就开始交叉比较了。首先是oldStartVnodenewEndVnode做比较。
  • 如果oldStartVnodenewEndVnode匹配不上的话,就oldEndVnodenewStartVnode进行比较。 
  • 此时,如果四种比较方法都匹配不到相同节点的话,我们就只能使用暴力解法去实现了,也就是针对于newStartVnode这个节点,我们去遍历oldCh中剩余的节点,一一匹配。可以先根据key 取匹配。

    对比算法 双针比较法

  • 四.key的作用

    key的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点,提高diff 对比效率。

    SameVnode 方法里对比是否是同一个的节点 会根据 节点身上的key属性进阶对比,key相同 ==> 可以相同节点 ===> 给列表项添加的唯一标识, 为了高效的更新虚拟DOM。

    4.1 无key

    最大限度尝试就地修改/复用相同类型元素

    4.2 有key, 值为索引

  • 插入新来的小li

    4.3 有key值,唯一不重复的字符串或数字

  • 时间复杂度

    算法两个指标 空间复杂度和时间复杂度

    空间复杂度:对一个算法在运行过程中临时占用存储空间大小的量度

    时间复杂度:指执行当前算法所消耗的时间

    时间复杂度 - 掘金

  • diff 算法的时间 复杂度

    为你这个 其实就是让你讲一下 diff 算法

    两个树的完全的diff算法是一个时间复杂度为 O(n3) ,Vue进行了优化·O(n3) 复杂度的问题转换成 O(n) 复杂度。

    传统的diff算法

    两棵树做 diff,复杂度是 O(n^3) 的,因为每个节点都要去和另一棵树的全部节点对比一次,这就是 n 了,如果找到有变化的节点,执行插入、删除、修改也是 n 的复杂度。所有的节点都是这样,再乘以 n,所以是 O(n * n * n) 的复杂度

    这样复杂度对于前端框架来说是不可接受的,这意味着 1000 个节点,渲染一次就要处理 1000 * 1000 * 1000,一共 10 亿次

  • Vue2 diff
    ●只比较同一层级,不跨级比较
    ●tag不相同,则会直接删除重建,不再深度比较
    ●tag和key都相同,则认为其为相同节点。

    这样只要遍历一遍,对比一下 tag 就行了,是 O(n) 的复杂度。而且 tag 变了就不再对比子节点,能省下一大片节点的遍历。另外,因为 vdom 中记录了关联的 dom 节点,执行 dom 的增删改也不需要遍历,是 O(1)的,整体的 diff 算法复杂度就是 O(n) 的复杂度

    DIV

    DIV

    DIV

    DIV

    DIV

    SPAN

    SPAN

    'DONG*

    'GUANG'

    image.png



    五.总结
    什么是虚拟dom?
    虚拟dom本质就是一个js对象,用来描述真正dom是什么样的,这个对象就称为虚拟dom

    为什么用虚拟dom?
    虚拟dom可以进行高效更新,同时也可以使用虚拟dom进行跨平台: 开发的代码只是模板代码 => 虚拟dom => web(编译为web的dom) => (小程序的节点)

    如何实现高效更新?
    初始化渲染的时候,会根据数据和模板生成一份虚拟dom树,当数据发生变化,会根据新的数据和模板生成新的虚拟dom树,将两份虚拟dom树进行对比,对比的算法采用的是diff算法

    diff算法?
    同级比较.深度优先,比较核心的就是采用了双指针算法,四个指针,遍历旧的虚拟dom有两个指针,指向开始的位置和结束的位置,同理新的虚拟dom也有这两个指针,循环的时候开始的指针对比完后,指针向后推,后面的指针对比后向前推,从而达到效率提升

    diff对比之后的情况?
    元素不同: 删除重建
    元素相同,属性不同: 元素复用,属性更新

    v-for:
    key 的作用: key的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点,提高diff 对比效率。
    有key:不建议使用索引,索引会变化,建议使用唯一值,对比的使用key进行对比
    无key, 看数据的变化是否影响到顺序,如果影响到顺序,影响到性能
    无key, 看数据的变化是否影响到顺序,如果没有影响到顺序,性能没有影响

     

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Blue_路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值