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