虚拟 DOM
虚拟DOM指的是使用纯js对象来表示一个节点的结构,该对象包含了真实DOM的结构与属性,通过对比新旧两个虚拟DOM,找出差异,然后修改旧的虚拟DOM(可以认为真实的DOM是基于旧的DOM来渲染的)。
虚拟DOM的优点
减少DOM操作
由于真实的DOM是在渲染引擎生成的,而通过JS来操作DOM则涉及到了跨线程的操作,速度会相比同线程操作来的慢,但是现代的网页更强调可交互性,所以操作DOM还是不可避免,那么我们可以减少操作DOM的次数,来提高性能。
方法一:虚拟 DOM 可以将多次 DOM 操作合并为一次操作 比如你有 1000 个 DOM 节点,通过浏览器提供的 DOM API 可能需要操作 1000 次,但是虚拟 DOM 可以合起来只操作 1 次。
方法二:DOM Diff 算法可以摒除多余的 DOM 操作 DOM Diff 算法,顾名思义就是看对比前后两次 DOM 树的区别,然后只去更新有变化的 DOM 节点。 但是,一旦DOM节点数非常高,DOM API 反而会快于虚拟 DOM,因为虚拟 DOM 自身存在大量计算。
虚拟DOM的样子
const vNode = {
tag: "div",
data: {
class: "red",
on: {
click: () => {}
}
},
children: [
{.......}
],
...
}
DOM diff
diff算法在执行时有三个维度,分别是Tree diff、Component diff和Element diff,执行时按顺序依次执行,它们的差异仅仅因为diff粒度不同、执行先后顺序不同。
Tree diff是对树的每一层进行遍历,如果某组件不存在了,则会直接销毁。如图所示,左边是旧属,右边是新属,第一层是R组件,一模一样,不会发生变化;第二层进入Component diff,同一类型组件继续比较下去,发现A组件没有,所以直接删掉A、B、C组件;继续第三层,重新创建A、B、C组件。
key值的问题
老集合中包含节点:A、B、C、D,更新后的新集合中包含节点:B、A、D、C,此时新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。 以上这类操作繁琐冗杂,一些节点只是换了位置而已却要经历低效的删除与操作。其实只要对这些节点进行位置移动即可。于是框架作者提出了优化的策略,比如vue的就地复用。 但是就地复用会导致一个问题,diff算法从左往右进行同层级对比,如果发现元素相同,而内容不相同,直接修改内容,这就会导致一个问题,明明我们删除的是这个节点,却发现是另一个节点被删除了。因此我们需要给每一个DOM节点添加一个唯一的标识,让diff算法能够正确识别DOM节点。