虚拟DOM

介绍

虚拟DOM是一个能够表示DOM树的对象,包含标签名,事件,属性,类名,子元素等等。

优势

  1. 减少DOM操作的次数,可以将多次的DOM操作合并到一次更新
  2. 减少DOM操作的范围,借助DOM diff把多余的操作去掉,譬如说更新1000个节点中的10个
  3. 能跨平台渲染

    缺点

    需要额外的创建函数,如React需要使用createElement创建虚拟DOM,但可以使用JSX(React)或模板语法(Vue)代替,然后通过构建工具进行转换。

    DOM diff

    介绍

    当我们拿到了新旧两个虚拟DOM树,自然就需要将这两者之间的变化应用到真实DOM当中,这个过程就是DOM diff

    大体思路

    Vue

    Vue:https://juejin.im/post/6844903607913938951
    Vue的diff算法策略是一边比较节点一边进行DOM更新,具体函数为patch.js

而比较策略则是采用同级比较的方法,参照下图:
163776ba7bda2d47.jpg
patch函数接收oldVnode和vnode参数,先判断是否为同一vnode,如果不是,那么直接将oldVnode的DOM元素替换为vnode的DOM元素,如果是,则进行使用patchVnode函数打补丁操作(更新属性、事件、子节点等等)

比较是否为同一vnode的逻辑如下:

  1. function sameVnode (a, b) {
  2. return (
  3. a.key === b.key && // key值
  4. a.tag === b.tag && // 标签名
  5. a.isComment === b.isComment && // 是否为注释节点
  6. // 是否都定义了data,data包含一些具体信息,例如onclick , style
  7. isDef(a.data) === isDef(b.data) &&
  8. sameInputType(a, b) // 当标签是<input>的时候,type必须相同
  9. )
  10. }

在patchVnode函数中,最重要的是updateChildren函数,此函数进行更新新旧vnode的子节点的操作。
这个函数的更新策略如下:

  1. 分别对比oldCh和ch头尾节点,存在头头,头尾,尾头,尾尾四种情况,如果命中则进行patchVnode操作
    1. 针对尾头情况,则将旧的DOM元素移动到头部
    2. 针对头尾情况,则将新的DOM元素移动到尾部
  2. 如果上述四种情况都不存在,那么就查找oldCh当中与当前vnode相同的oldVnode,策略:检查vnode是否存在key值,如果存在key值,则通过查oldCh的哈希表(key -> index)查询是否存在此key,如果不存在key值,那么就遍历oldCh查询第一个相同的vnode
    1. 如果不存在一个相同的vnode,那么就直接创建新的元素插入到父元素中
    2. 如果存在相同vnode,那么就patchVnode
  3. 最后就检查是否存在没插入的元素或者多余的还没被删除的元素,进行统一处理

    React

    React:https://zhuanlan.zhihu.com/p/20346379
    在React中,diff策略(旧版,新版为Fiber)可以分为三个部分:
  • tree diff
  • component diff
  • element diff
    tree diff
    与Vue相似,React会对新旧两棵树进行逐层比较,如果是组件节点,则走component diff流程,如果是元素节点,则走element diff流程
    component diff
    判断是否为同一类型组件,如果不同则直接替换,如果相同则更新属性事件等,然后继续进行tree diff操作
    element diff
    如果是原生标签,则看标签名,标签不同则替换,相同则更新属性等,然后继续进行tree diff

与Vue类似,在更新子元素的时候,React当中会根据每个元素的key值来判断是否为同一个元素,以此来实现对旧元素的复用。

缺陷

DOM diff算法面对大量相同类型的节点变更时,会无法有效地复用已有的节点,譬如下面这个例子:
v2-6e88cc53a7e427f0ae8340cf930ac30d_720w.png
向列表中插入一个元素F,理想的状态是直接复用节点,然后插入新元素,但实际上是这样的:
v2-bf76311258f100b789226ccbb2600071_r.png
由于都是同类型的元素,DOM diff无法精准识别出当前节点跟哪个旧节点相同,只会模糊地认为当前元素标签名与自身一致,就认为是同一节点,直接进行更新。
为了解决这种情况,Vue和React都引入了key值来标识节点,使算法能根据key值精准识别节点,从而使得就有节点能合理复用。
v2-bb1147af7c458f0b09d6a3c2f74b0100_r.png