PatchFlags优化
- Diff算法无法避免新旧虚拟DOM中无用的比较操作,通过patchFlag来标记动态内容,可以实现快捷diff算法
我们模拟一个template模板内容被编译成虚拟节点后的内容
vue处理后的虚拟节点打印结果
- 标识动态节点,也就是dynamic开头的属性里的内容,在上面例子中,我们只比较dynamicChildren里的动态内容
- 而不是去传统的比较children里的内容,也就是无需先去比较h1节点,再去比较span
总结
这样性能就比较好了,不是去进行两棵树,而是去比较某个节点了,这个就是我们所谓的靶向更新,例子中的block有动态收集节点的能力,这个block可以收集所有的动态节点。这样后续更新的时候可以直接跳过静态节点,实现靶向更新
接下来我们去实现一个简易版
- 我们需要实现openBlock、createElementBlock、toDisplayString、createElementVNode
openBlock
- 用一个数组来收集多个动态节点
createElementBlock
- 内部还是调创建虚拟节点的方法createVnode,参数比createVnode多了一个patchFlag,传入createVnode,然后在创建的虚拟节点上加patchFlag标识,标识它哪些会变
- 把createVnode创建好的虚拟节点传入setupBlock方法里,这个方法主要是在虚拟节点上加dynamicChildren,它的值就是当前的currentBlock
createElementVNode
- createElementVNode实际上就是我们之前写过的创建虚拟节点的函数,它俩是一个东西,只是不同的名字
toDisplayString
- 把拿到的值处理成字符串
下面我们就进行动态节点的收集了
- 在创建虚拟节点方法的最后加上判断
- 在创建虚拟节点的时候我们判断如果有currentBlock,并且虚拟节点上有patchFlag大于0,说明有标识,那么我就把当前的的虚拟节点收集到currentBlock里
更新的时候进行处理,判断虚拟节点是否有dynamicChildren
- 更新流程中patchChildren方法,我们之前这里的实现实际是一个全量的diff算法,其实我们没必要全量diff
patchBlockChildren方法
- 循环新的节点里的dynamicChildren
- 只去比对新旧虚拟节点中dynamicChildren里的内容
- 以前是树的递归比较,现在是数组的比较,肯定数组的性能好
上述只是元素的靶向更新,下面我们实现props属性的靶向更新
- 这个span就标记了一个2,标识它的class属性可能会发生变化
我们在上述判断dynamicChildren的逻辑前进行判断
- 判断是否匹配到了动态class的标识
- 以前我们对属性也是全量比对的,现在如果命中了动态class的标识,我们就只笔记新旧虚拟的class,而不是全量的属性比对
- 类似的我们还可以对style、事件等等去专项的进行判断
如果是不稳定结构
- 当flag改变的时候,p是不会变成div的,那要怎么处理呢
vue3的做法
- 最外层的block收集两个block,p标签看做一个整体,div看做一个整体,这两个block又收集它们各自的动态节点,这就形成了一个block tree
- 不稳定结构的,都要产生block
- 每次渲染都会收集对应的block
v-for
- 如果循环的数据是动态的,会把所有循环子项全部放在一个block里面,里面的子项也就不具备靶向更新的能力了
- 如果循环的项是固定的,比如我就写死循环3个,那么还是走稳定结构的情况,里面的子项具备靶向更新的能力
总结:
- v-if和v-for会产生额外的block,防止靶向更新出问题
静态提升
- 每次重新渲染调render方法后,我们都要调createElementVNode方法,比较消耗性能
静态提升后
- 把创建静态节点的结构拿出去,每次调render,我就取上次的结构就可以了
- 上述例子中,我把创建hello所在的标签和创建的静态属性拿了出去
如果你的静态文本超过20个
- 会把所有相同的静态文本字符串化,用的话性能更好
对函数的优化
- 对函数优化前,你每次render后,都创建了一个新函数,对性能是有消耗的
我们可以把函数给缓存起来
- 把函数缓存起来,进行复用
vue3模板编译优化
- 增添了patchflag来标识哪些节点是动态的
- block来收集节点,为不稳定结构的也创建了block节点,实现blockTree,做到靶向更新
- 最后进行优化(静态提升,属性的提升和虚拟节点的提升,函数的缓存,预解析字符串)
- 写模板的性能会比直接写h函数更好一些