根本解法是放弃全量加载,改用流式分页+增量处理+及时释放引用:通过Fetch+ReadableStream逐段读取、服务端游标分页避免OFFSET、前端每批处理后切断引用链,并可结合Web Worker转移计算任务。

直接在浏览器中处理百万级数据极易触发内存溢出,根本原因在于 JavaScript 单线程 + 堆内存有限(通常 512MB–2GB,且受标签页限制),而一次性加载全部数据到内存(如 fetch 后 JSON.parse)会瞬间占满可用空间。真正的解法不是“优化游标”,而是**放弃全量加载,改用流式分页 + 增量处理 + 及时释放引用**。
用 Fetch + ReadableStream 实现数据流式读取
服务端需支持分块返回(如按行、按记录或按固定大小 chunk),前端用 response.body.getReader() 逐段读取,不等待全部响应完成:
- 服务端响应头设
Content-Type: application/json-seq或自定义格式(如每行一个 JSON 对象) - 前端用
const reader = response.body.getReader()获取流读取器 - 循环调用
reader.read(),每次只解析当前 chunk 中的若干条记录(例如 100 条),处理完立即丢弃引用 - 避免拼接完整字符串——用
TextDecoder流式解码,边读边切分,不缓存整段文本
后端配合:提供游标分页接口(非 OFFSET/LIMIT)
前端传 cursor(如上一页最后一条记录的 ID 或时间戳),后端用 WHERE 条件高效查询下一批数据:
- 示例请求:
GET /api/data?cursor=123456&limit=500 - 数据库查询用
WHERE id > ? ORDER BY id LIMIT ?,避免OFFSET越往后越慢 - 每次响应返回新 cursor(下一批起始点)和当前批次数据,前端仅保留必要字段,不深拷贝
- 服务端响应体控制在 100KB 内,降低单次内存压力
前端增量处理与内存管控
每批数据处理完必须主动切断引用链,防止 V8 垃圾回收延迟:
立即学习“前端免费学习笔记(深入)”;
- 用
for...of或普通for遍历数组,避免map/filter/reduce创建中间数组 - 处理函数内不闭包捕获大批量数据;用
let声明临时变量,作用域结束即释放 - 批量渲染时用
document.createDocumentFragment()统一插入,避免反复触发重排 - 监听
beforeunload或定期检查performance.memory(若支持),接近阈值时暂停加载并提示用户
替代方案:Web Worker + Transferable Objects
将解析、计算等重任务移出主线程,利用 postMessage 传递 ArrayBuffer 等可转移对象,实现零拷贝:
- 主线程 fetch 二进制数据(
response.arrayBuffer()),用worker.postMessage(buffer, [buffer])直接移交所有权 - Worker 内用
Uint8Array解析,逐块转为 JS 对象,处理完即delete引用 - 结果以小批次形式发回主线程渲染,避免 Worker 内存堆积
- 适用于 CSV/JSONL 等结构化二进制流,比纯 JSON 字符串节省约 30% 内存
不复杂但容易忽略:浏览器崩溃往往不是因为“数据太大”,而是“同一时刻驻留内存的对象太多”。控制住单次驻留量、切断引用、让 GC 能及时工作,百万级数据就能稳稳跑在页面里。


















