背景
前端遇到大数据量的场景,为了提高用户的使用体验,我们需要做很多优化手段。
我们可以从以下的防线去优化:
- 接口层
- 分段获取
- 数据层
- 分段存储
- 提高索引速度
- 字典树
- 提高存取速度
- 渲染层
本文围绕渲染层的优化来讲解
思维链
1、懒加载
初始化只渲染首屏的数据,当用户滚动时,渲染额外数据。
优点
- 首屏加载快
缺点
- 随着用户的持续性滚动,真实dom和内存会越来越多
- 滚动过快有白屏
2、延迟加载
首屏正常渲染,渲染完后,异步渲染其他部分。
我们往往会使用requestAnimationFrame、requestIdleCallback、setTimeout等
特性 | requestAnimationFrame | requestIdleCallback | setTimeout |
---|---|---|---|
优点 | • 浏览器优化,每帧执行一次 • 不会造成丢帧 • 节省系统资源,页面不是激活状态时,动画暂停 | • 在浏览器空闲时执行 • 不影响用户交互和动画 • 可以设置超时时间 | • 使用简单 • 兼容性好 • 可以设置延迟时间 |
缺点 | • 不能设置执行时间间隔 • 动画的开始/取消需要手动控制 • 兼容性不如setTimeout | • 兼容性最差 • 执行时机不可控 • 可能永远不会执行 | • 精度不高 • 容易造成丢帧 • 嵌套调用可能会导致调用栈溢出 |
优点
- 很大程度解决了懒加载的问题
缺点
- 一开始内存占用就很大
3、虚拟加载
无论用户滚动到哪个位置,都只渲染用户可视区域的内容
优点
- 真实dom少
- 内存占用固定且少
缺点
- 滚动过快有白屏
4、canvas
直接抛弃真实dom的渲染,使用canvas来渲染,渲染性能非常强。
优点
- 渲染快
缺点
- 实现异常复杂
5、异步分片
- 大量真实dom的渲染,可以拆分成多个渲染子任务;
- 对于canvas的渲染,我们可以拆分成多个珊格,从而拆分成多个渲染子任务;
这些渲染子任务队列如果一次性执行,会导致js单线程的卡顿,所以我们需要控制每一帧里执行的子任务的时间,比如每一帧为16ms,那么我们可以拿其中的10ms来执行子任务,剩下的6ms来响应用户的交互,这样就不会导致js单线程的卡顿。
ts
// 模拟一个耗时任务队列
const tasks = new Array(10000).fill(0).map((_, index) => () => {
// 模拟每个任务的执行
let result = 0;
for(let i = 0; i < 10000; i++) {
result += Math.random();
}
console.log(`完成任务 ${index}`);
});
class TimeSliceExecutor {
constructor(tasks) {
this.tasks = tasks;
this.timeLimit = 10; // 每帧分配10ms执行任务
}
// 执行任务队列
execute() {
if (this.tasks.length === 0) {
console.log('所有任务执行完成');
return;
}
const startTime = performance.now();
while (this.tasks.length > 0) {
// 检查是否超过当前帧的时间限制
if (performance.now() - startTime > this.timeLimit) {
// 超过时间限制,在下一帧继续执行
requestAnimationFrame(() => this.execute());
break;
}
// 执行一个任务
const task = this.tasks.shift();
task();
}
}
// 开始执行
start() {
requestAnimationFrame(() => this.execute());
}
}
// 使用示例
const executor = new TimeSliceExecutor(tasks);
executor.start();