🔧 本系列定位 :生产级实战 · 可落地代码
📘 想先快速掌握面试考点和架构思维?推荐阅读姊妹篇:
《大列表性能优化 · 面试精讲》(三篇轻快认知)
生产环境不死机 ------ 监控、降级与异常兜底
实验室里的优化很美好,但用户的低端机、网络波动、内存不足都会让方案失效。
真正的工程能力,体现在"优雅降级"和"兜底设计"上。
一、内存监控与自动降级
即使我们用了位图、分片加载,在某些极端场景下(比如用户不断滚动加载所有分片,或者数据量超过 20 万),内存仍可能飙升。
1.1 使用 performance.memory 监控
javascript
function checkMemoryAndDegrade() {
if (performance.memory && performance.memory.usedJSHeapSize > 200 * 1024 * 1024) {
console.warn('High memory usage, triggering degradation');
// 触发降级策略
degrade();
}
}
setInterval(checkMemoryAndDegrade, 30000);
1.2 降级策略实现
javascript
function degrade() {
// 1. 清理非可视区缓存分片
if (chunkedLoader) chunkedLoader.cache.clear();
// 2. 隐藏非可视区图片(如果有)
document.querySelectorAll('img[data-lazy]').forEach(img => {
if (img.getBoundingClientRect().top > window.innerHeight * 2) {
img.src = '';
}
});
// 3. 提示用户刷新或切换模式
showToast('内存占用过高,建议刷新页面或开启精简模式');
}
二、低端机识别与精简模式
并不是所有用户都有高端设备。我们可以通过 navigator.deviceMemory 判断设备内存(Chrome 支持)。
javascript
function isLowEndDevice() {
// deviceMemory 单位 GB,可能为 undefined
const memory = navigator.deviceMemory;
if (memory && memory < 4) return true;
// 也可结合 CPU 核心数 navigator.hardwareConcurrency
if (navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4) return true;
return false;
}
if (isLowEndDevice() && totalDataCount > 50000) {
showModal({
title: '精简模式推荐',
content: '检测到您的设备内存较小,开启精简模式将只展示前1000条数据,并提供搜索功能。',
onConfirm: () => enableLiteMode()
});
}
function enableLiteMode() {
// 只加载前 1000 条数据,停用虚拟滚动,使用普通分页
loadInitialData(1000);
document.body.classList.add('lite-mode');
}
三、Web Worker 超时与降级
Worker 可能因为复杂计算或用户设备过慢而长时间未响应。设置超时,超时后降级回主线程执行。
javascript
function sortDataWithFallback(field, order) {
return new Promise((resolve, reject) => {
const taskId = Date.now();
let resolved = false;
// 超时定时器
const timeoutId = setTimeout(() => {
if (!resolved) {
console.warn('Worker timeout, fallback to main thread');
// 降级:主线程排序
const sorted = [...fullData];
sorted.sort((a, b) => {
if (a[field] < b[field]) return order === 'asc' ? -1 : 1;
if (a[field] > b[field]) return order === 'asc' ? 1 : -1;
return 0;
});
resolve(sorted);
resolved = true;
}
}, 2000);
worker.postMessage({ type: 'SORT', payload: { field, order, taskId } });
worker.onmessage = (e) => {
if (e.data.type === 'SORT_RESULT' && e.data.taskId === taskId && !resolved) {
clearTimeout(timeoutId);
resolve(e.data.indices);
resolved = true;
}
};
});
}
四、滚动白屏兜底
虚拟滚动在快速滚动时可能来不及渲染数据,出现白屏。解决方案:
- 预渲染更多缓冲区(例如上下各 2 屏)
- 骨架屏:数据未加载时展示占位骨架
- 重试机制:如果某个切片加载失败,重新请求
javascript
// 骨架屏示例
function renderSkeleton(start, end) {
for (let i = start; i < end; i++) {
const row = document.createElement('div');
row.className = 'skeleton-row';
row.style.height = `${itemHeight}px`;
container.appendChild(row);
}
}
五、页面卸载时主动释放资源
内存泄漏往往发生在页面跳转后。务必在 beforeunload 或组件卸载时清理:
javascript
window.addEventListener('beforeunload', () => {
if (worker) {
worker.terminate();
worker = null;
}
// 清空大型数组引用
fullData = null;
selection = null;
// 取消所有 pending 请求
if (currentAbortController) currentAbortController.abort();
});
在 Vue/React 中,放在生命周期钩子中:
javascript
// React
useEffect(() => {
return () => {
worker?.terminate();
};
}, []);
六、监控埋点建议
生产环境需要可见性,建议上报以下指标:
| 指标 | 采集方式 | 告警阈值 |
|---|---|---|
| 虚拟滚动帧率 | requestAnimationFrame 计数 |
< 30fps |
| Worker 排序响应时间 | 记录 postMessage 到 onmessage 耗时 |
P99 > 200ms |
| 内存峰值 | performance.memory.usedJSHeapSize |
> 300MB |
| 位图全选耗时 | performance.now() |
> 100ms |
| 降级触发次数 | 代码埋点 | 日均 > 1% 用户 |
七、总结
从"能跑"到"高可用",需要回答以下问题:
- 内存超了怎么办? → 降级清理缓存
- 低端机卡顿怎么办? → 精简模式
- Worker 挂了怎么办? → 超时降级主线程
- 滚动白屏怎么办? → 骨架屏 + 预加载
- 页面关了内存没释放? → 终止 Worker,清空引用
*真正的 能力 ,不是背出虚拟滚动的 API,而是能体系化地解决:
内存管理、线程同步、数据结构选型、降级设计、监控兜底。
系列回顾
- 第1篇:大列表性能优化 · 工程实战·一 (理解瓶颈本质 )
- 第2篇:大列表性能优化 · 工程实战·二(位图存储 + 分片加载 )
- 第3篇:大列表性能优化 · 工程实战·三 (Web Worker 计算隔离 + 虚拟滚动联动 )
- 第4篇:大列表性能优化 · 工程实战·四 (监控、降级、生产落地 )