vue 的虚拟 dom 差异比较和更新差异
发布于 4 年前 作者 yuanxiuying 4104 次浏览 来自 分享

# 1.虚拟 dom 到底是什么?

>   虚拟 dom 是对真实 dom 的对象描述,这个对象可以称为 vnode。那么 vnode

>   是怎么产生的又有什么属性呢?

>   Vue 通过调用 createElement 函数来返回 vnode,createElement 函数大致如下

>   简易流程图如下

>   vnode 的定义则是在 types 中的 vnode.d.ts 文件,如下

-   tag: 当前节点的标签名

-   data: 当前节点的数据对象

-   children: 数组类型,包含了当前节点的子节点

-   text: 当前节点的文本,一般文本节点或注释节点会有该属性

-   elm: 当前虚拟节点对应的真实的dom 节点

-   ns: 节点的namespace

-   context: 编译作用域

-   functionalContext: 函数化组件的作用域

-   key: 节点的 key 属性,用于作为节点的标识,有利于patch 的优化

-   componentOptions: 创建组件实例时会用到的选项信息

-   child: 当前节点对应的组件实例

-   parent: 组件的占位节点

-   raw: raw html

-   isStatic: 静态节点的标识

-   isRootInsert: 是否作为根节点插入,被\<transition\>包裹的节点,该属性的值为

    false

-   isComment: 当前节点是否是注释节点

-   isCloned: 当前节点是否为克隆节点

-   isOnce: 当前节点是否有v-once 指令

>   而其中最为重要的 data 的定义如下

# 虚拟 dom diff 算法

### diff 算法包括一下几个步骤:

-   用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM

    树,插到文档当中

-   当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较(diff),记录两棵树差异

 -   把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM

      树上(patch),视图就更新了

而且,diff算法是通过**同层的树节点**进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法

>   函数 patch 的核心部分如下

### patch 函数做了以下事情:

-   使用samevnode 比较新旧vnode,只有相同,才会使用patchVnode函数进行深入节点比较

-   否则,取得 oldvnode.el 的父节点,parentEle 是真实 dom,createEle(vnode)会为 vnode 创建它的真实 dom,令 vnode.el =真实 dom,

parentEle 将新的 dom 插入,移除旧的dom。也就是说使用新节点直接将老节点替换掉。

### patchVnode 函数做了以下事情:

-   找到对应的真实dom,称为 el

-   判断 Vnode 和oldVnode 是否指向同一个对象,如果是,那么直接 return

-   如果他们都有文本节点并且不相等,那么将 el 的文本节点设置为 Vnode的文本节点。

-   如果oldVnode 有子节点而 Vnode 没有,则删除el 的子节点

-   如果oldVnode 没有子节点而 Vnode 有,则将 Vnode 的子节点真实化之后添加到 el

 -   如果两者都有子节点,则执行updateChildren 函数比较子节点,这一步很重要

-   其他几个点都很好理解,我们详细来讲一下updateChildren

>   上面代码很长,但是逻辑很清晰

>   代码里分别有四个坐标,分别为 oldStart+oldEnd,newStart+newEnd。然后处理如下

1.  处理四个指标分别为null 的情况,如果是 end

    则使用前一个节点,如果是start,则使用后一个节点

2.  处理头部的同类型节点,即 oldStart 和 newStart 指向同类节点的情况

3.  处理尾部的同类型节点,即 oldEnd 和 newEnd 指向同类节点的情况

4.  处理头尾/尾头的同类型节点,即 oldStart 和 newEnd,以及 oldEnd 和 newStart

    指向同类节点的情况

 5 上面四种情况之外的情况

5.1  尝试在oldStart+oldEnd 之间 中寻找跟 newStartVnode 具有相同 key

    的节点,如果找不到相同 key 的节点,说明 newStartVnode

    是一个新节点,就创建一个,并插入 oldStartVnode.elm 之前然后把 newStartVnode

    设置为下一个节点

5.2  如果找到了跟 newStartVnode 相同 key 的节点,那么通过其他属性的比较来判断这 2

    个节点是否是同一个节点,如果是,就调用 patchVnode 进行patch,并把

    newStartVnode.elm 插入到 oldStartVnode.elm 之前,把newStartVnode

    设置为下一个节点

    最后,可能会出现两种情况,一个是,oldStartIdx \> oldEndIdx,这说明旧节点列表

    oldch 先一步被循环完毕,此时新节点列表 newch

    剩下的没有被循环到的也就是newStart+newEnd 之间的节点会被插入到 newEndIdx+1

    的节点之前。另一种是新

    节点列表 newch 先循环完毕,那么将旧节点列表 oldch 中未被处理的也就是oldstart

    和oldend 之间的节点删除(注意,删除时由于第 5 第 2)

    步中将被匹配到的旧节点设置为了

    null,以此来标记此节点已经被处理过,因此它不会被删除)。

回到顶部