《Vue.js设计与实现》笔记 第11章:快速 Diff 算法

Vue.js设计与实现 第11章:快速 Diff 算法

本章导读

Vue3 最终 Diff 方案:

Fast Diff

+

Longest Increasing Subsequence

(LIS)

目标:

减少DOM移动

提高更新性能

整体流程:

预处理

↓

同步头部

↓

同步尾部

↓

处理中间乱序部分

↓

LIS

↓

移动DOM

一、为什么需要快速Diff

双端Diff的问题

例如:

A B C D E

↓

D C B A E

虽然:

节点都存在

但:

大量查找

大量移动

性能仍不理想。


Vue3优化目标:

减少查找

减少移动

二、快速Diff整体结构

分为五步:

1 同步前置节点

2 同步后置节点

3 处理新增

4 处理删除

5 处理中间乱序

三、同步前置节点

示例

旧:

A B C D

新:

A B E F

从头开始:

A == A

B == B

直接:

patch()

继续:

C != E

停止。


结果:

前面公共部分已处理

四、同步后置节点

示例

旧:

A B C D

新:

E F C D

从尾开始:

D == D

C == C

直接:

patch()

继续:

B != F

停止。


结果:

后面公共部分已处理

五、为什么先处理头尾

因为真实项目中:

列表变化

通常发生在中间

例如:

头部稳定

尾部稳定

中间变化

这样:

大量节点无需参与Diff

六、处理新增节点

示例

旧:

A B

新:

A B C D

头尾同步后:

旧节点已处理完

剩余:

C D

直接:

patch(
 null,
 vnode
)

挂载。


七、处理删除节点

示例

旧:

A B C D

新:

A B

同步后:

新节点已结束

剩余:

C D

执行:

unmount()

删除即可。


八、中间乱序节点

最复杂情况:

旧:

A B C D E

新:

A D C B E

头尾同步后:

处理中间:

B C D

↓

D C B

这部分才是真正Diff重点。


九、建立Key索引表

为什么建立Map

旧方案:

查找节点

O(n)

Vue3:

Map<
 key,
 index
>

例如:

{
 D:2,
 C:1,
 B:0
}

查找:

map.get(key)

复杂度:

O(1)

十、source数组

作用

记录:

新节点

对应旧节点位置

例如:

旧:

B C D

新:

D C B

结果:

source = [
  2,
  1,
  0
]

表示:

D来自旧索引2

C来自旧索引1

B来自旧索引0

十一、判断是否需要移动

示例

source = [
  0,
  1,
  2
]

说明:

完全递增

不需要移动。


示例

source = [
  2,
  1,
  0
]

说明:

顺序已改变

需要移动。


十二、最长递增子序列(LIS)

核心优化。


什么是LIS

Longest Increasing Subsequence

最长递增子序列


例如:

[
 2,
 3,
 1,
 5,
 4
]

LIS:

[
 2,
 3,
 5
]

因为:

保持递增

且:

长度最长

十三、为什么需要LIS

示例

source = [
  2,
  3,
  1,
  4
]

LIS:

[
  2,
  3,
  4
]

表示:

这些节点位置正确

无需移动。


只移动:

1

对应节点。


十四、LIS核心思想

保留不动节点

例如:

A B C D E

变化后:

A D B C E

LIS:

B C

说明:

B

C

已经处于正确顺序

无需移动。


只移动:

D

十五、倒序遍历

Vue3最终:

for(
 i = count-1
 i >=0
 i--
)

原因:

插入节点时

锚点稳定

方便移动。


十六、移动节点

找到锚点

例如:

anchor =
 nextVNode.el

执行:

insert(
 vnode.el,
 container,
 anchor
)

完成:

DOM移动

十七、完整流程

同步头部

↓

同步尾部

↓

新增

↓

删除

↓

建立索引表

↓

生成source

↓

计算LIS

↓

移动节点

十八、时间复杂度

查找

Map:

O(1)

遍历

O(n)

LIS

O(n log n)

最终:

O(n)

级别。


远优于:

简单Diff

O(n²)

十九、Vue3为什么快

核心原因:

1 Map索引

2 头尾预处理

3 LIS优化

4 最少DOM移动

真正节省的不是:

JS计算

而是:

DOM操作

第二十章核心执行流程

旧VNode

↓

同步头

↓

同步尾

↓

处理中间

↓

Map

↓

source

↓

LIS

↓

移动DOM

↓

完成更新

第11章核心知识图谱

Fast Diff

│

├── 同步头部

├── 同步尾部

├── 新增

├── 删除

│

└── 中间乱序

       │

       ▼

     Map

       │

       ▼

    source

       │

       ▼

      LIS

       │

       ▼

   DOM Move

高频面试题

Vue3为什么不用双端Diff?

因为:

Fast Diff

+

LIS

性能更好

Fast Diff优化点有哪些?

同步头

同步尾

Map索引

LIS

source数组作用?

记录:

新节点对应旧节点位置

什么是LIS?

最长递增子序列。


为什么使用LIS?

找到:

不需要移动的节点

减少:

DOM移动次数

LIS优化的本质是什么?

让尽可能多节点保持原位

Vue3 Diff时间复杂度?

O(n)

实际:

O(n log n)

主要来自LIS。


为什么倒序遍历?

为了:

保证锚点稳定

方便插入。


本章总结

Vue3 Fast Diff流程:

同步头

↓

同步尾

↓

新增

↓

删除

↓

Map索引

↓

source数组

↓

LIS

↓

DOM移动

核心优化:

Map

+

LIS

目标:

最少DOM移动

这是 Vue3 Diff 算法性能优于 Vue2 的关键原因。

理解:

source

LIS

为什么要倒序遍历

基本就掌握了 Vue3 Diff 的核心思想。

posted @ 2025-06-03 14:29  Li_pk  阅读(4)  评论(0)    收藏  举报