上节大致讲了一下snabbdom的用法,文章尾部弄了一个实例,今天我们拿那个列表的来分析如何做diff渲染。

关键知识点

这个diff算法有点类似我们平常玩的拼图游戏,拼图游戏就是给定一个图像,然后看你拼的和正确答案差多少。不过在diff算法里面会同时从首尾进行逐个比对。

第一种情况:你拼的图像的首和正确的首一样(或者尾巴相同),则比较下一个,图形表示则大概如下所示:

background Layer 1 正确答案 A B .......... new_start new_end 你的答案 A C .......... old_start old_end 正确答案 C K .......... new_start new_end 你的答案 D K .......... old_start old_end 首相同 尾相同 同时右移,比较下一个 同时左移,比较上一个

意思很简单,就是你同时比对你的拼图和正确答案的拼图的首或者尾,如果你发现同首或者同尾则可以比较下一个了(首相同,右移动,尾相同,左移动)

第二种情况,如果你拼图中的首和正确拼图中的尾相同(或者你的尾和正确的首相同),那么你需要进行移动,图形表示大致如下:

background Layer 1 正确答案 A B .......... new_start new_end 你的答案 C A .......... old_start old_end 正确答案 C K .......... new_start new_end 你的答案 K D .......... old_start old_end 你的尾=正确的首 你的首=正确的尾 相等 移动到C之前 将old_end移动到old_start之前 相等 移动到D之后 将old_start移动到old_end之后

上面两种情况比较难以理解,但是仔细想想其实也是可以理解的,目的是为了你的拼图更加接近正确的拼图,移动之后,可以保证有一边是相同的。

第三种情况,如果你的拼图的首尾和正确拼图的首尾各不相同,那么你就去你的拼图还没比对的区域去找正确拼图中的首,如果找到了,移动它;如果没有找到,插入一个正确的首,大致如下所示:

background Layer 1 正确答案 A B .......... new_start new_end 你的答案 C K ..... old_start old_end 正确答案 A B .......... new_start new_end 你的答案 C K .......... old_start old_end 正确的首,存在[old_start,old_end]之间 将new_start拷贝一个放到old_start之前 ..... A 存在 移动到old_start之前 正确的首,不存在[old_start,old_end]之间 把你的答案中相同的,移动到old_start之前 拷贝new_start 到old_start之前

这种情况其实也是可以理解,目的也和上一个一样,也是起码保证一边是相等的。

上面所讲的拼图其实和diff算法类似,可能你还不是很懂,下面我们就结合真正的例子,走一遍流程,走完你就会豁然开朗。

第一步,替换原始节点

我们知道,既然叫做diff渲染,那么就表示有两个h函数生成的数据进行比较,但是第一次是没有两个数据的,只有一个数据,我们经常会看到如下结构:

我们可以看到上面第一次渲染的时候,是利用生成的DOM结构体和container比较,在此次比较过程中,会替换掉container:

替换前:<div id='container'></div>

替换后:

增加元素

在列表中,增加节点的关键代码如下:

主要就是在data数组中增加一个元素,那么这个是如何进行diff操作的呢?

background Layer 1 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 1 2 3 old_start old_end new_end new_start

上面是开始有两个元素,后来变成三个的diff准备操作,这里我们要对这个准备操作做一些说明:

1.旧的虚拟DOM刚开始和真实的DOM结构是一样的,旧的虚拟DOM结构只是起一个辅助作用,它负责和新的虚拟DOM结构进行比对,有差异的地方修改的是真实DOM结构;

2.diff比对规则和之前讲的拼图是一样的,这里旧的DOM结构等同于你的拼图,新的DOM结构等同于正确的拼图。注意,在这里比较两个元素是否相等看的是修饰符(如h('div#ab'),那么div#ab就是修饰符)和key,如果这两个相同,那么就认为是同一个元素。

3.如果old_start>end_start或者new_start>new_end则表示对比结束,需要处理剩下的内容。

background Layer 1 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 1 2 3 old_start old_end new_end new_start old_start和new_start相同,右移 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 1 2 3 old_start old_end new_end new_start old_start和new_start相同,右移 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 1 2 3 old_start old_end new_end new_start 对比结束,将[new_start,new_end]放入old_end(对应的真实DOM)之后 在2后面增加一个3

我们可以发现,增加元素的时候,old_start会大于old_end触发停止对比,就好比拼图的时候,你自己的拼图已经对比完了,但是正确的拼图上还有好几块没对比,那么这个时候要把正确拼图上没有比对完的放到你的拼图上(old_end)之后。

删除元素

列表中,删除用户的关键代码如下:

我们来看看指针是如何移动的:

background Layer 1 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 1 2 old_start old_end new_end new_start 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 1 2 old_start new_end new_start old_start和new_start相同,右移 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 1 2 3 old_start old_end new_end new_start 对比结束,将[old_start,old_end]之间的元素(对应真实DOM)删除 3 old_end 3 old_start和new_start相同,右移 3 3 3

和增加元素不同,删除元素是new_start>new_end触发停止,拿拼图举例子,就是正确的拼图已经对比完了,但是你的拼图上还有好几块,所以你要把你的拼图上没有对比的删除掉。

交换元素

在列表中,交换元素体现的就是排序了,关键的排序代码如下:

排序较增加,删除复杂一些,现在我们来看看这个是如何移动的:

background Layer 1 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 3 1 old_start old_end new_end new_start 真实DOM结构 3 1 真实DOM结构 3 3 2 2 new_start等于old_end old_end放到old_start前面 new_start++ old_end-- 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 3 1 old_start old_end new_end new_start 3 2 old_end等于new_end old_end-- new_end-- 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 3 1 old_start old_end new_end new_start 3 2 old_start等于new_start old_start++ new_start++ 真实DOM结构 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 3 1 old_start old_end new_end new_start 3 2 old_start>old_end同时new_start>new_end 完结,没有额外操作 3 1 2 3 1 2

复杂案例

通过上面的几个小案例,我们应该有点眉目了,现在我们来个复杂的练练手。

background Layer 1 真实DOM结构 1 2 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 old_start old_end new_end new_start 3 3 4 5 6 7 8 4 5 6 7 8 2 5 6 7 8 9 相等 new_start存在[old_start,old_end]内 将该元素移动到old_start之前 真实DOM结构 2 1 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 old_start old_end new_end new_start 3 3 4 5 6 7 8 4 5 6 7 8 2 5 6 7 8 9 new_start++ 该元素变成undefined(这里用虚线表示) 变成undefined表示该元素被操作过,下次跳过就好 new_start不存在[old_start,old_end]内 将该元素拷贝一个移动到old_start之前 new_start++ 真实DOM结构 2 1 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 old_start old_end new_end new_start 3 3 4 5 6 7 8 4 5 6 7 8 2 5 6 7 8 9 new_start等于old_end 将old_end移动到old_start之前 new_start++ 9 相等 old_end-- 真实DOM结构 2 1 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 old_start old_end new_end new_start 3 3 4 5 6 7 8 4 5 6 7 8 2 5 6 7 8 9 new_start等于old_end 将old_end移动到old_start之前 new_start++ 9 old_end-- 相等 真实DOM结构 2 1 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 old_start old_end new_end new_start 3 3 4 5 6 7 8 4 5 6 7 8 2 5 6 7 8 9 new_start等于old_end 将old_end移动到old_start之前 new_start++ 9 old_end-- 相等 真实DOM结构 2 1 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 old_start old_end new_end new_start 3 3 4 5 6 7 8 4 5 6 7 8 2 5 6 7 8 9 new_start等于old_end 将old_end移动到old_start之前 new_start++ 9 old_end-- 相等 真实DOM结构 2 1 旧的虚拟DOM结构 1 2 新的虚拟DOM结构 old_start old_end new_end new_start 3 3 4 5 6 7 8 4 5 6 7 8 2 5 6 7 8 9 new_start>new_end触发停止 将[old_start,old_end]删除 9 注意里面的2,因为它已经被处理过了,现在是undefined 所以删除它并不会关联到真实DOM的2 删除 删除 diff后的真实DOM结构 2 5 6 7 8 9

其他

真正的diff是多层级,树形结构的,上面讲的都是单层的,其实了解了单层,多层无非就是多了一个递归而已。

background Layer 1 1 2 3 21 22 23 3 2 1 21 22 23 同级比对 同级比对

每次diff的时候,就算元素没有变化,也会去判断style,class,event等是否发生变化,一旦有发生变化,立马更新。

其他文章

0
我要评论

评论

返回
×

我要评论

回复:

昵称:(昵称不超过20个字)

图片:

提交
还可以输入500个字