虚拟dom和diff算法
https://github.com/livoras/blog/issues/13
这里简单记录一些要点和理解:
一个dom元素中有许多属性,操作dom是很耗资源的,而操作自定义的js对象是很高效。所以在操作dom之间多加一层“虚拟dom”,建立虚拟dom与dom的关联,把直接操作dom转换为操作虚拟dom,然后把最终的虚拟dom关联到dom上,关联的方式是把差异应用到dom上。
一个js对象来描述一个dom,只需要tagName、props以及children即可。
一个虚拟dom应用,有如下初始化过程:
- 通过js(如react的jsx)确定好虚拟dom
- 根据虚拟dom生成实际的dom树,写到body中
在js中对虚拟dom进行操作,每次操作会生成一颗新的虚拟dom树,虚拟dom的新树和旧树进行对比,找出差异,然后这些差异会被应用到实际的dom上,完成界面的变更。
对比方式:
同层次节点对比,深度优先。
差异的类型以及处理方式:
- 标签名变更,则整个节点统一进行替换,里面的子节点也跟着替换。
- 标签的属性变更,把变更的属性应用上去。
- 文本节点内容变更,直接替换即可。
- 子元素个体的增加、删除、移动。
如何检测子元素个体的变更?
为每个个体都加上一个标识符key,在当前兄弟节点中这个key要唯一,这样才能在当前的所有children中唯一标识。标识完成后,问题就可以转化为字符串的对比问题了,这里对比只能得出列表的差异(增加删除移动等)。接着继续进行相同key的节点的对比,到这里可见差异的对比是递归的。进行子元素个体的标识,有利于dom的复用,如果不指定,算法会认为两个子元素列表完全不一样,会全部重新渲染,这就很耗费性能了。
“如果元素没有重排,使用数组的索引作为key效果不错”。如何理解这句话呢?
没重排的意思是,结构体的展示次序不发生变化,而仅仅是结构体中少量属性发生变更。在这种情况下,两个列表元素依次一一对应,找出差异,然后把这些差异按次序应用到列表dom上。如果元素有重排,而且使用了索引作为key,两个列表中相同索引的结构体会完全不同,这样一进行对比,可能会得出一大堆的差异,再将这些差异应用上去可能会比较慢,我感觉其实这些说的都只是相对而已。
如何把差异应用(patch)到实际dom上?
最开始初始化的时候,根据虚拟dom生成实际的dom,两者的结构层次是一样的,而差异是通过对虚拟dom深度优先对比出来的,应用当然是对实际dom进行深度优先,然后把差异应用上去。