简介

说明

        本文介绍vue的diff的原理。

        vue的diff原理是基于虚拟DOM的。​

diff原理简介

初始渲染

        第一次渲染页面时,按此流程进行:模板=> 渲染函数=> 虚拟DOM=> 真实DOM

        虚拟DOM是用JS对象来表示的,里边的每一个节点可以称为虚拟节点:Vnode。虚拟DOM可以理解为一个树状结构的对象。

页面改动

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

        把所记录的差异应用到真正的DOM树上(patch),视图就更新了。

diff原理详解

diff的两个特点


  1. 比较只会在同层级进行, 不会跨层级比较
  2. 在diff比较的过程中,循环从两边向中间比较

流程详述

        采用深度优先遍历,把树形结构按照层级分解,只比较同级元素;当数据发生改变时,set方法会让调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁(两个重要函数patchVnode和updateChildren)。


  1. 判断根结点及变化后的节点是否是sameVnode。

  1. 如果不是的话,就会创建新的根结点并进行替换
  2. 如果是sameVnode,则进入patchVnode函数。

  1. patchVnode函数

  1. 如果两个节点是相等oldVnode === vnode则直接return
  2. 如果新节点是文本节点,则判断新旧文本节点是否一致,不一致(oldVnode.text !== vnode.text)则替换
  3. 如果新节点不是文本节点,则开始比较新旧节点的子节点oldCh和ch:
  4. 如果子节点都存在,则进行updateChildren计算(稍后讲)
  5. 如果只有新子节点存在,则如果旧节点有文本节点,则移除文本节点,然后将新子节点拆入
  6. 如果只有旧子节点存在,则移除所有子节点
  7. 如果均无子节点且旧节点是文本节点,则移除文本节点(此时新节点一定不是文本节点)

  1. updateChildren函数(做细致对比)

  1. start && oldStart对比
  2. end && oldEnd对比
  3. start && oldEnd对比
  4. end && oldStart 对比
  5. 生成map映射,(key:旧子节点上的key,value:旧子节点在自己点中的位置),根据key记录下老节点的位置(idxInOld)
  6. 如果找到了idxInOld,如果是相同节点则移动旧节点到新的对应的地方,否则虽然key相同但元素不同,当作新元素节点去创建
  7. 如果没有找到idxInOld,则创建节点
  8. 如果老节点先遍历完,则新节点比老节点多,将新节点多余的插入进去
  9. 如果新节点先遍历完,则就节点比新节点多,将旧节点多余的删除


diff中key的作用

diff算法比较后,会出现3种情况:


  1. 如果新虚拟DOM找到了与旧虚拟DOM相同的key,但新旧虚拟DOM内容没有变,新的虚拟DOM就不用创建真实DOM,直接复用之前旧虚拟DOM创建的真实DOM。
  2. 如果新虚拟DOM找到了与旧虚拟DOM相同的key,但新旧虚拟DOM内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
  3. 如果新虚拟DOM中未找到与旧虚拟DOM相同的key,则创建新的真实DOM,随后渲染到页面

最后总结,key正是diff算法的关键,通过这个key可以作为桥梁,连接起新旧两个虚拟DOM。

diff原理示意图

新旧节点的比较方法如下:


  1. 先找到不需要移动的相同节点,消耗最小
  2. 再找相同但是需要移动的节点,消耗第二小
  3. 最后找不到,才会去新建删除节点,保底处理

比如下图存在这两棵 需要比较的新旧节点树 和 一棵 需要修改的页面 DOM树

Vue--diff的原理_javascript

第一轮比较

        因为父节点都是 1,所以开始比较他们的子节点。按照我们上面的比较逻辑,所以先找 相同 && 不需移动 的点。

        毫无疑问,找到 2。

Vue--diff的原理_删除节点_02

拿到比较结果,这里不用修改DOM,所以 DOM 保留在原地。

Vue--diff的原理_javascript_03

第二轮比较开始

        此时没有 相同 && 不需移动 的节点 了。只能第二个方案:找相同的点。

        找到 节点5,相同但是位置不同,所以需要移动。

Vue--diff的原理_子节点_04

 拿到比较结果,页面 DOM 树需要移动DOM 了,不修改,原样移动

Vue--diff的原理_前端_05

第三轮比较开始

        此时相同节点也没得了,只能创建了。要根据 新Vnode 中没找到的节点去创建并且插入。

        然后旧Vnode 中有些节点不存在 新VNode 中,所以要删除。

Vue--diff的原理_删除节点_06

于是开始创建节点 6 和 9,并且删除节点 3 和 4 

Vue--diff的原理_javascript_07