一、原因
- 因为操作真实dom的性能消耗会很大,所以vue为了尽量的减少真实dom的操作,特别是新增和删除,
- 所以它会这样:在「对比子节点」时,vue一切的出发点,都是为了:
- 1、尽量啥也别做。
- 2、不行的话,尽量仅改动元素属性。
- 3、还不行的话,尽量移动元素,而不是删除和创建元素。
- 4、还不行的话,删除和创建元素。
二、目的
- 渲染真实DOM的开销是很大,
为了实现最小量更新。 - 以某种策略找到
新旧两种数据(虚拟DOM)状况的不同来实现最小量更新的办法。
三、发生
- 当我们当前组件所依赖的数值更新和组件创建时运行update函数。
- update函数会调用组件的render函数,render生成新的虚拟dom树,
- update得到新虚拟dom树的根节点,然后进入update函数内部,将旧虚拟dom树替换成新的虚拟dom树,然后用一个变量将旧虚拟dom树保存起来,接下来调用patch函数进行diff比对。
四、算法规则
- 旧DOM
没有子元素,新DOM有子元素,清除旧文本 生成新节点 - 旧DOM
有子元素, 新没有子元素,清除旧节点 生成新文本 - 旧DOM
没有子元素,新DOM没有子元素,对比文本 进行更新 - 旧DOM
有子元素,新DOM有子元素,进行diff算法对比
五、是否相同
- 1、标签类型相同、key相同、如果是input也要比较type是否相同,都相同那就是相同,否则就是不同
- 2、确定相同,进行属性比较,进行属性更新
- 3、不同,按顺序继续处理
六、优化策略 - 顺序 - 双端比较法
- 当oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx时结束循环。
1、头头比较
- (1)、相同,把对应的DOM元素移动到新的节点上,新指针旧
向右移1个,继续比较,如果一直比较到其中一个虚拟DOM树结束都是相同,比较结束,如果有某个虚拟DOM树还有节点没有比较,如果是新虚拟DOM树直接在节点上新增DOM元素,如果是老的直接删除节点和对应DOM元素。 - (2)、不相同,走下面2。
2、尾尾比较
- (1)、和上面的方式一样,是相同的新旧指针都
向左移1个,继续比较,不同走下面的3。
3、头尾比较(旧,新)
- (1)、相同,把旧节点上DOM元素移动到对应的新节点上,旧头指针后移、新尾指针前移,一直到某一个结束,和1结束一样处理
- (2)、不同,走下面的4
4、尾头比较(旧,新)
- (1)、和上面的方式一样,相同就移动DOM到新的节点上,旧尾指针前移、新头指针后移,一直到某一个结束,和1结束一样处理
- (2)、不同,走下面的5
5、以新树头节点为基准查找旧树
- (1)、拿新数组的第一个节点去老数组中去查找,找不到,在新指针上创建新的DOM,新指定向右移,继续从1开始比较。
- (2)、找到了,移动旧指针的DOM元素到新指针上,然后新指针向后移,继续从1开始比较,旧指针对他的节点设置undefined
七、key 的作用
- key 作为节点的唯一标识,在 Diff 过程中能够帮助 Vue 2 准确地复用相同的节点。当节点有 key 时,Vue 可以根据 key 快速找到可复用的节点,避免不必要的 DOM 操作。
八、其他作者总结
1、初始化指针 :为新旧子节点列表分别设置首尾指针。
2、 循环比较 :在oldStartIdx <= oldEndIdx 且 newStartIdx <= newEndIdx 条件下,持续比较:
- **头头比较** :若旧头与新头节点相同,更新节点,指针后移。
- **尾尾比较** :若旧尾与新尾节点相同,更新节点,指针前移。
- **头尾比较** :若旧头与新尾节点相同,更新节点并移动 DOM 位置,旧头指针后移、新尾指针前移。
- **尾头比较** :若旧尾与新头节点相同,更新节点并移动 DOM 位置,旧尾指针前移、新头指针后移。
- **其他情况** :在旧列表找与新头节点 `key` 相同的节点,有则移动更新,无则创建新节点插入。
3、循环结束处理 :
- 若旧列表先遍历完,将新列表剩余节点插入 DOM。
- 若新列表先遍历完,移除旧列表剩余节点对应的 DOM。