vue diff 原理详解

62 阅读4分钟

1. patch()———— 初步比较是否是同一个节点

  • 首先进入patch方法,会传入老节点和新节点。
  • 如果老节点存在,新节点不存在,择代表销毁老节点
  • 如果新节点存在,老节点不存在,这是初次渲染。直接创建dom
  • 如果老节点不是真实元素,sameVnode去比较是否是 同一个节点,如果是则代表是同一个节点需要更新,这就走到diff的流程了.patchVnode()
  • 如果老节点是真实元素——只有服务端渲染流程会出现
  • 如果老节点不是真实元素,且sameVnode比较不通过。说明元素改变了不是同一个节点,就根据新的node创建元素插入父节点,同时移除老节点

2. sameVnode()———— 比较是否是同一节点的方法


function sameVnode (a, b) {
  return (
    // key 必须相同,需要注意的是 undefined === undefined => true
    a.key === b.key && (
      (
        // 标签相同
        a.tag === b.tag &&
        // 都是注释节点
        a.isComment === b.isComment &&
        // 都有 data 属性
        isDef(a.data) === isDef(b.data) &&
        // input 标签的情况
        sameInputType(a, b)
      ) || (
        // 异步占位符节点
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
  • 首先判断KEY,如果undefined,则相当于一直都相等,所以要加key
  • 然后判断标签类型、是否注释、是否有data属性,input标签还会额外判断type是否一样

3. patchVnode 详细比较node更新,子节点递归

  • 首先判断是否完全相等,完全相等直接返回。如果不完全一致就进行详细的属性比较和更新。 包含新的位置、文本、属性等
  • 然后比较子节点。老节点没有孩子,新节点有则新增。老节点有新节点没有则删除。 其中如果都有 childeren 的话,就递归调用 updateChildren 去比较子节点。

4. updateChildren 子节点diff流程

  • 旧头旧尾新头新尾4个指针
  • 在不为undefined的情况下:
    • 旧头新头比较: 先sameVnode初步比较是否是同一节点。是:则patchVnode详细比较更新节点同时比较下一层子节点。旧头+1,新头+1
    • 旧尾新尾比较:sameVnode=>是:patchVnode,旧尾-1,新尾-1
    • 旧头新尾比较:sameVnode=>是:patchVnode,移动元素到新尾,旧头+1,新尾-1
    • 旧尾新头比较:sameVnode=>是:patchVnode,移动元素到新头,旧尾-1,新头+1
    • 上面四种IF没过,用新头的key去老节点中找对应的节点。没找到或者没有key=》新建dom。找到:sameVnode=>否:不是同一中节点,当成新建的;是:patchVnode,移动dom到老头,旧index=undefined(因为这个老节点已经在新节点中用到了,所以下一轮比较的时候直接跳过这个老节点),新头+1
  • 下一轮比较
  • 当头尾节点重合或者超过长度,代表已经比较完。新节点有剩余代表有新增,老节点有剩余代表删除则移除

5. 流程总结:

  • 先patch函数,判断是否有新老节点,然后根据判断新增、删除、或者进行diff
  • diff过程中,判断是否是同节点复用的依据就是sameVnode。里面通过key、tag、inputtype等条件判断
  • 如果sameVnode为否则当成是新元素,走创建dom。如果是则patchVnode。
  • patchVnode里面进行详细的属性内容比较,并且更新。切如果有子节点,还会进行子节点的比较updateChildren
  • updateChildren就是核心的diff流程。
  • 在diff的时候,如果需要移动元素位置,这一操作是在patchVnode理进行的。.就是把老节点的dom直接放到新节点上

6. 关于加 key 的问题。

关于加 key 的问题。因为 sameVnode 里面,首先会判断 key 是不是相等,然后再判断 tag 和其他的一些条件,如果是用 v-for 列表循环出来的,往往元素除了内容以外都是一样的。如果不加 key,sameVnode 就会判断通过,初步认为两个节点是一样的,就会就地复用而不是重新创建元素。这样,比如删除掉中间一个节点的话,diff 的时候头头比较因为没有 key,tag 和其他条件也一样,就误认为全部相同,然后修改内容,直到最后少了一个,认为是最后一个元素被删除了。

也就是说这种情况下的流程是。假设删除第 3 个节点

老节点 0 - 新节点 0 =》 相同,然后详细判断,文本内容不一样,修改文本

老节点 1 - 新节点 1 =》 相同,然后详细判断,文本内容不一样,修改文本

老节点 2 - 新节点 2 =》 相同,然后详细判断,文本内容不一样,修改文本

老节点 3 - 新节点 3 =》 相同,然后详细判断,文本内容不一样,修改文本

老节点 4 - 无 =》 新节点少一个,判断为已删除,所以删除最后一个

  • 值得注意的是,因为虽然复用了元素,但是根据数据渲染出来的内容其实还是正常的,每个节点都会修改。但是如果有 input 输入框或者 checkbox 勾选这种是元素里带的属性,就会发生数据混乱了.必须是dom带的临时数据才会!!!!!!!

  • 且,用 index 做 key,因为增删元素 index 自然也会变,所以等同于没有 key