diff
面试官:你了解vue的diff算法吗?说说�?

一、是什�?
diff 算法是一种通过同层的树节点进行比较的高效算�?
其有两个特点�?- 比较只会在同层级进行, 不会跨层级比�?- 在diff比较的过程中,循环从两边向中间比�?diff 算法在很多场景下都有应用,在 vue 中,作用于虚�?dom 渲染成真�?dom 的新�?VNode 节点比较
二、比较方�?
diff整体策略为:深度优先,同层比�?
比较只会在同层级进行, 不会跨层级比�?
比较的过程中,循环从两边向中间收�?

下面举个vue通过diff算法更新的例子:
新旧VNode节点如下图所示:

第一次循环后,发现旧节点D与新节点D相同,直接复用旧节点D作为diff后的第一个真实节点,同时旧节点endIndex移动到C,新节点�?startIndex 移动到了 C

第二次循环后,同样是旧节点的末尾和新节点的开�?都是 C)相同,同理,diff 后创建了 C 的真实节点插入到第一次创建的 D 节点后面。同时旧节点�?endIndex 移动到了 B,新节点�?startIndex 移动到了 E

第三次循环中,发现E没有找到,这时候只能直接创建新的真实节�?E,插入到第二次创建的 C 节点之后。同时新节点�?startIndex 移动到了 A。旧节点�?startIndex �?endIndex 都保持不�?
第四次循环中,发现了新旧节点的开�?都是 A)相同,于�?diff 后创建了 A 的真实节点,插入到前一次创建的 E 节点后面。同时旧节点�?startIndex 移动到了 B,新节点的 startIndex 移动到了 B

第五次循环中,情形同第四次循环一样,因此 diff 后创建了 B 真实节点 插入到前一次创建的 A 节点后面。同时旧节点�?startIndex 移动到了 C,新节点�?startIndex 移动到了 F

新节点的 startIndex 已经大于 endIndex 了,需要创�?newStartIdx �?newEndIdx 之间的所有节点,也就是节点F,直接创�?F 节点对应的真实节点放�?B 节点后面

三、原理分�?
当数据发生改变时,set方法会调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,更新相应的视�?
源码位置:src/core/vdom/patch.js
1 | function patch(oldVnode, vnode, hydrating, removeOnly) { |
patch函数前两个参数位为oldVnode �?Vnode ,分别代表新的节点和之前的旧节点,主要做了四个判断:
- 没有新节点,直接触发旧节点的
destory钩子 - 没有旧节点,说明是页面刚开始初始化的时候,此时,根本不需要比较了,直接全是新建,所以只调用
createElm - 旧节点和新节点自身一样,通过
sameVnode判断节点是否一样,一样时,直接调�?patchVnode去处理这两个节点 - 旧节点和新节点自身不一样,当两个节点不一样的时候,直接创建新节点,删除旧节�?
下面主要讲的是patchVnode部分
1 | function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { |
patchVnode主要做了几个判断�?
- 新节点是否是文本节点,如果是,则直接更新
dom的文本内容为新节点的文本内容 - 新节点和旧节点如果都有子节点,则处理比较更新子节�?- 只有新节点有子节点,旧节点没有,那么不用比较了,所有节点都是全新的,所以直接全部新建就好了,新建是指创建出所有新
DOM,并且添加进父节�?- 只有旧节点有子节点而新节点没有,说明更新后的页面,旧节点全部都不见了,那么要做的,就是把所有的旧节点删除,也就是直接把DOM删除
子节点不完全一致,则调用updateChildren
1 | function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { |
while循环主要处理了以下五种情景:
- 当新�?
VNode节点�?start相同时,直接patchVnode,同时新�?VNode节点的开始索引都�?1 - 当新�?
VNode节点�?end相同时,同样直接patchVnode,同时新�?VNode节点的结束索引都�?1 - 当�?
VNode节点�?start和新VNode节点�?end相同时,这时候在patchVnode后,还需要将当前真实dom节点移动�?oldEndVnode的后面,同时�?VNode节点开始索引加 1,新VNode节点的结束索引减 1 - 当�?
VNode节点�?end和新VNode节点�?start相同时,这时候在patchVnode后,还需要将当前真实dom节点移动�?oldStartVnode的前面,同时�?VNode节点结束索引�?1,新VNode节点的开始索引加 1 - 如果都不满足以上四种情形,那说明没有相同的节点可以复用,则会分为以下两种情况�? - 从旧�?
VNode�?key值,对应index序列�?value值的哈希表中找到�?newStartVnode一�?key的旧�?VNode节点,再进行patchVnode,同时将这个真实dom移动�?oldStartVnode对应的真�?dom的前�? - 调用createElm创建一个新�?dom节点放到当前newStartIdx的位�?
小结
- 当数据发生改变时,订阅者
watcher就会调用patch给真实的DOM打补�?- 通过isSameVnode进行判断,相同则调用patchVnode方法 patchVnode做了以下操作�? - 找到对应的真实dom,称为el- 如果都有都有文本节点且不相等,将
el文本节点设置为Vnode的文本节�? - 如果oldVnode有子节点而VNode没有,则删除el子节�? - 如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el - 如果两者都有子节点,则执行
updateChildren函数比较子节�?-updateChildren主要做了以下操作�? - 设置新旧VNode的头尾指�? - 新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻�?key一致的VNode节点再分情况操作
- 如果都有都有文本节点且不相等,将