虚拟dom
- 把Dom抽象成js对象来表示。
- 当页面数据变化,将新虚拟dom和旧虚拟dom比对,得出差异,然后只修改差异部分,局部更新,减少重复创建渲染相同dom的损耗
- 直接操作dom是要比虚拟dom快很多的,但是当节点很多,需要局部更改,虚拟dom可能是最优的,可以说虚拟dom是一种性能损耗和时间之间比较平衡的方法
虚拟dom优点:
- 减少不必要的dom操作
- 跨平台(本质就是js对象模拟了dom)
虚拟dom缺点:
- 需要额外的创建函数如vue的h函数和react的creatElement函数
- 如何解决?:
- react有jsx,vue有template,但是这两个的缺点就是需要额外的构建过程,把它转换为标签。
更新流程
Dep.notify通知watcher进行更改
patch()
patch()
- 对比新旧两个虚拟节点
- 如果没有旧节点,直接插入新节点
- 如果没有新节点,直接删除旧节点
- 如果新旧节点都存在
- 使用
sameVnode
函数进行比对,看是否为可比较节点(是否相同)- key是否相同
- tag是否相同
- 是否都为注释节点
- 是否都定义了data(不在意data是否相同)
- inoput的type是否相同
- 相同:调用
patchVnode
进一步处理 - 不同:直接删老添新
- 使用
patchVnode
- 两者一模一样(dom节点的引用判断),直接返回
- 两者在生命周期created和beforemount间变为AST后,被优化时标记为了静态节点,并且两者key相同,则直接吧旧节点赋给新节点,然后返回
- 如果新节点是文本节点,直接设置进真实dom里
- 如果不是文本节点,进行以下比对
- 如果都有孩子节点且孩子节点不同,调用
updateChildren
函数进行孩子更新 - 如果只有新孩子,若旧节点有文本节点,删除,插入新节点的孩子节点
- 如果只有旧孩子,移除旧孩子
updateChildren
- 设置四个指针,指向旧节点头尾,新节点头尾
- while循环比对,四个指针向中间靠拢,当旧尾小于旧头或新尾小于新头,结束循环
- (旧节点先结束,说明新节点有需要插入的系内容;新节点先结束,说明旧节点有需要删除的旧内容)
- 旧头新头,比对成功,两者指针+1,向中间靠拢
- 旧尾新尾,比对成功,两者之间-1,向中间靠拢
- 旧头新尾,比对成功,旧头+1,新尾-1,向中间靠拢
- 旧尾新头,比对成功,旧尾-1,新头+1,向中间靠拢
- 四种情况都没成功
- 根据旧节点生成key与value的map
- 新头的key在map中是否有对应的key
- 对应成功,移动旧节点中匹配上的,新头+1
- 未成功,生成新节点并插入,新头+1
在这期间,判断两者相同还是用sameVnode,改变两者还是用patchVnode
v-for为什么用key标识
在标签为li
情况下:
根据updateChildren函数,如果无key,即都为undefined,则每次比较时key都相同(sameVnode
认为两者相同),则会进入深入处理的patchVnode函数,然后把两者内部的文本进行替换,复用当前的li
元素。
假设:A B C D变为A B E C D
则A,B不变,当到E时,发现E与C的key一样(undefined),进入patchVnode函数,会直接把E内的文本替换到C内,这就导致E根本不是E,而是换了文本的C(换皮了,根没变),这就会导致一些错误(你选择了E,其实你选的还是C)
而加上了key,每个li
都有自己的key
假设:A B C D变为A B E C D
A,B同,到E时,不满足E等于C(key不同),则变道第二种比对(旧头旧尾),然后最后发现旧节点先结束,说明新节点中E多余,是新插入的,则生成E再插入,这样就对了