前言
- 常网IT源码上线啦!
- 本篇录入吊打面试官专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
- 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
- 接下来想分享一些自己在项目中遇到的技术选型以及问题场景。
人生如debugger。
这个过程,可能充满了挫败感,甚至会让你怀疑人生。但每一次成功的"debug",每一次对自身"源代码"的优化,都会让你的人生程序运行得更顺畅,更高效,也更接近你内心真正渴望的那个"理想版本"。

一、前言
我们知道,虚拟DOM和Diff算法是实现高效渲染的核心机制。Vue和React作为最流行的两大框架,在Diff算法的实现上各有特色。本文将深入剖析Vue2/Vue3和React的Diff算法原理,对比它们的优化策略。
直入正文。
vue2和Vue3和React的diff算法。
二、Diff算法基础
传统Diff算法需要对两棵树进行完全比较,时间复杂度高达O(n³),这太慢了。前端框架通过以下假设将复杂度降低到O(n):
-
同层级比较:只比较同一层级的节点,不跨层级比较
-
类型相同则复用:相同类型的组件/元素会复用而不是重建
-
Key值优化:使用key标识节点身份,提高复用准确性
核心Diff流程
不管是vue还是react,基本流程都是:
-
同级比较:首先比较新旧虚拟DOM树的同级节点
-
子节点比较:对相同节点的子节点进行递归比较
-
差异应用:将计算出的差异应用到真实DOM
三、Vue的Diff算法演进
关于这个问题,我确实想了几秒钟。因为有vue2和vue3。
Vue2的双端比较算法
Vue2采用了双端比较策略,通过四个指针同时从新旧子节点的两端向中间移动:
java
function updateChildren(parentElm, oldCh, newCh) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let newEndIdx = newCh.length - 1
// ...比较逻辑
}
比较步骤:
-
头头比较:oldStartVnode vs newStartVnode
-
尾尾比较:oldEndVnode vs newEndVnode
-
头尾交叉:oldStartVnode vs newEndVnode
-
尾头交叉:oldEndVnode vs newStartVnode
-
Key映射查找:建立旧节点key到index的映射表查找可复用节点
优势:
- 对列表头尾操作(如push/pop/shift/unshift)有极致优化
- 减少不必要的节点移动
Vue3的最长递增子序列优化
Vue3在Vue2基础上引入最长递增子序列(LIS)算法进一步优化:
java
// 获取最长递增子序列
function getSequence(arr) {
const p = arr.slice()
const result = [0]
// ...动态规划实现
return result
}
优化点:
-
静态标记:编译时标记静态节点,跳过Diff
-
Block Tree:将动态节点组织为区块,仅对比动态部分
-
位运算:使用位运算快速判断VNode类型
-
Patch Flags:细粒度标记需要更新的属性
性能对比:
-
相同场景下,Vue3比Vue2减少约30%的DOM操作
-
大型列表更新速度提升2-3倍
四、React的Diff
React采用递归同层比较策略:
-
元素类型不同:直接卸载重建整个子树
-
元素类型相同:比较属性并更新
-
列表比较:依赖key值进行最小化移动
局限性:
-
对列表中间插入操作效率较低
-
递归不可中断,可能导致卡顿
Fiber架构的革命性改进
React 16引入Fiber架构解决上述问题:
核心机制:
-
任务分片:将渲染工作拆分为多个5ms左右的小任务
-
优先级调度:
arduino
const ImmediatePriority = 1 // 用户输入
const UserBlockingPriority = 2 // 交互反馈
const NormalPriority = 3 // 普通更新
const LowPriority = 4 // 预加载
-
链表遍历:通过child/sibling指针实现可中断遍历
-
双缓冲技术:current树(当前UI)和workInProgress树(构建中)
五、Vue与React Diff的核心差异
列表对比策略对比
场景:将列表[A,B,C,D]变为[D,A,B,C]
框架 | 操作次数 | 具体操作 |
---|---|---|
Vue | 1 | 移动D到头部 |
React | 3 | 移动A/B/C各一次 |
节点复用策略
Vue:
-
className不同视为不同类型节点
-
严格依赖key值匹配
React:
-
className不同仍可能复用
-
key不是必须但强烈推荐
静态优化机制
Vue的编译时优化:
csharp
// 编译时标记静态节点
_hoisted_1 = createVNode("div", null, "Static Content")
React的运行时优化:
javascript
// 需要手动优化
const MemoComp = React.memo(() => <div>Static Content</div>)
六、实践
Key的使用原则
-
唯一稳定:使用唯一且不变的标识(如ID)
-
避免索引:不要用数组索引作为key
-
相同类型:相同类型的兄弟节点必须有key
减少Diff范围
Vue:
-
合理使用v-once
-
拆分小组件利用局部更新
React:
-
合理使用React.memo
-
避免在渲染函数中创建新对象
复杂列表优化
-
虚拟滚动(vue-virtual-scroller/react-window)
-
分页加载
-
时间切片(React并发模式)
七、React的Fiber调度
已经是讲完了,但针对Fiber我还是想多说说。
React 的 Fiber 调度机制是其运行时性能优化的核心实现,主要解决大规模应用中的渲染卡顿问题。
可中断的任务分片
php
传统方式(React 15 及之前):
一次性递归处理整个组件树 → 主线程阻塞 → 动画/输入卡顿
Fiber 模式(React 16+):
将虚拟DOM节点转化为链表结构的Fiber节点 → 可暂停/恢复遍历 → 每帧只处理 5ms 任务
如果用代码解释的话
java
// 当同时触发用户输入和大型列表更新时:
function App() {
const [list, setList] = useState([]);
// 高优先级更新(用户输入)
const handleInput = (e) => { /* 即时响应 */ };
// 低优先级更新(大数据加载)
const loadData = () => {
fetchData().then(data => {
React.startTransition(() => { // 标记为过渡更新
setList(data); // 可中断的渲染
});
});
};
}
我用一个现实场景帮你理解:
假设用户在表格中同时进行以下操作:
-
输入数据(高优先级)
-
加载万行数据(低优先级)
-
自动保存(中优先级)
传统方式就像这样:
scss
// 同步执行的伪代码
function 处理更新() {
处理输入() // 用户会感觉卡住
渲染万行数据()
自动保存()
}
Fiber 调度机制的工作原理:
java
// 分片执行的伪代码
function 处理更新() {
while (有时间 && 有任务) {
const 任务 = 获取最高优先级任务()
switch(任务.类型) {
case '用户输入':
立即处理输入()
break
case '自动保存':
if(超过5ms) yield 让出主线程
处理保存()
break
case '大数据渲染':
yield 让出主线程 // 遇到用户输入时中断
分块渲染()
}
}
requestIdleCallback(处理更新) // 浏览器空闲时继续
}
至此撒花~
后记
我感觉现在AI这么火,是不是后面可以让AI加入,机器学习预测,预测用户行为预加载内容。
我们在实际项目中或多或少遇到一些奇奇怪怪的问题。
自己也会对一些写法的思考,为什么不行🤔,又为什么行了?
最后,祝君能拿下满意的offer。
我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车
👍 如果对您有帮助,您的点赞是我前进的润滑剂。
以往推荐
前端哪有什么设计模式(14k+)