Diff算法

150 阅读4分钟

虚拟DOM:

  • 定义:vdom是对dom的js抽象表示,能够描述DOM结构和关系。最少包含三个属性:tag、props、children。应用的各种状态变化会作用于vdom,最终映射到真实dom上

  • 核心思想:对复杂的DOM结构,提供一种方便的工具,进行最小化的DOM操作

  • 优点:

    • 轻量、快速:数据改变时,对比新旧两个vdom,得出最小的dom操作,提升性能

    • 跨平台:将vdom转换为不同运行时特殊操作实现跨平台

    • 兼容性:加入兼容性代码可以增强操作的兼容性

image.png

#### vue中的diff算法
  • 概念:diff算法是一种优化手段,是虚拟DOM技术的必然产物。将前后两个模块进行差异化对比,修补差异化的过程叫做patch。
  • 本质:diff算法的本质是找出两个对象之间的差异,目的是尽可能复用节点
  • 为什么用:在vue2.x中降低了watcher的粒度,每个组件只有一个watcher,为了更精确地找到变化的位置,我们需要diff
  • 什么时候用:数据改变的时候,由于数据响应式会触发setter,setter会触发通知,将watcher加入异步更新队列(相同的watcher只能入列一次)。在每次事件循环结束时清空队列,执行watcher的run方法,调用组件的渲染函数render和更新函数update,重新渲染虚拟dom,比较新旧虚拟dom,触发diff算法
  • 怎么用:diff算法整体遵循深度优先、同层比较的策略。两个节点之间会根据是否有子节点或文本节点做不同操作。比较两组子节点是算法的重点。首先假设头尾节点可能相同做4次对比尝试,如果没有找到相同节点才按照通用方式递归查找。查找结束剩余的节点会做删除或追加操作。整个过程中,借助key可以非常精确的找到相同节点

整体流程:

  1. /src/core/instance/lifecycle.js:渲染/更新组件,执行patch方法

image.png 2. 生成vdom:/src/core/instance/render.js

image.png 3. diff算法:/src/core/vdom/patch.js
树级别的比较:新树不存在删除,老树不存在新增,都存在执行diff更新

image.png

patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)

比较两个vnode,原则:深度优先,同级比较,包括三种类型操作:属性更新、文本更新、子节点更新

具体规则如下:

  1. 如果新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren
  2. 如果老节点没有children子节点,而新节点有,先清空老节点的文本内容,再为其新增子节点
  3. 如果老节点有children子节点,而新节点没有,则移除该节点的所有子节点
  4. 如果新老节点都没有children子节点,文本替换

image.png

updateChildren:主要作用是用一种比较高效的方式对比新旧两个vnode的children得出最小的操作补丁

image.png

image.png

image.png 在新旧vnode左右头尾各有两个指针,在遍历过程中,4个指针逐渐向中间靠拢,当oldStartIdx > oldEndIdx,或者 newStartIdx > newEndIdx时,循环结束

遍历规则:4个指针两两交叉对比,4中比较规则

  1. oldStartVnode === newStartVnode或者oldEndVnode === newEndVnode,满足sameVNode,直接将该vnode节点执行patchVnode

image.png 2. oldStartVnode === newEndVnode,说明oldStartVnode已经跑到oldEndVnode后边,执行patchVnode同时还需要将真实DOM节点移动到oldEndVnode后边

image.png 3. oldEndVnode === newStartVnode,说明oldEndVnode已经跑到oldStartVnode前边,执行patchVnode同时还需要将真实DOM节点移动到oldStartVnode前边

image.png 4. 以上三种均不符合,则在old VNode中找与newStartVnode满足sameVNode的VNodeToMove,若存在,则执行patchVnode,同时将VNodeToMove对应的DOM移动到oldStartVnode对应的DOM前边

image.png 5. newStartVnode在old VNode中没有找到相同的key,或者key相同却不是sameVNode,调用createElm创建一个新的DOM节点,将新的DOM插入到oldStartVnode对应的DOM前边

image.png

循环结束,处理剩下的节点

  1. 当oldStartIdx > oldEndIdx,旧的VNode遍历结束,新VNode还没有结束,说明新VNode节点比老VNode多,调用addVnodes方法,将剩下的VNode对应的DOM插入到真实DOM中

image.png 2. 当newStartIdx  > newEndIdx,新VNode遍历结束,老遍历结束还有剩余,需要将老VNode删除

image.png