根本原因不是URL.createObjectURL,而是将大字符串一次性传入Blob构造函数导致同步拷贝阻塞主线程;应改用ReadableStream+WritableStream分块写入,或Worker分片构造Blob并合并。
直接用 URL.createObjectURL 导出几 MB 以上的 CSV,页面大概率假死——这不是 API 本身的问题,而是你把整段字符串一次性塞进了 Blob 构造函数。浏览器在生成 Blob 时会同步拷贝全部数据,内存峰值飙升,主线程被阻塞。真正该拆解的是「数据组装」和「分块写入」环节,URL.createObjectURL 只负责最后一步“挂载”,它本身极轻量。
现代浏览器(Chrome 109+、Edge 109+、Firefox 117+)已支持 WritableStream 和 TextEncoderStream,可避免拼接超长字符串。核心思路是:让 CSV 行数据作为流源,通过 TransformStream 做换行/转义处理,最终流入 new WritableStream({ write }),每写入一块就调用 controller.enqueue()。
关键点:
ReadableStream 的 pull 回调里控制数据节流,比如每次只取 1000 行,防止单次 pull 过载Array.prototype.join('n') 拼全部内容;改用 TextEncoder.encode(row + 'n') 单行编码后直接 write()
Blob,再用 URL.createObjectURL(new Blob([], { type: 'text/csv;charset=utf-8' })) 占位,后续流写入完成后才触发下载若需支持 Safari 或老版 Chrome,不能依赖流 API。可行方案是把数据切片,在 Web Worker 中分批构造小 Blob,再用 Promise.all 并行生成所有分片的 URL.createObjectURL,最后用 new Blob([blob1, blob2, ...]) 合并——注意:合并操作仍会触发一次内存拷贝,所以单个分片建议 ≤ 2MB。
立即学习“前端免费学习笔记(深入)”;
常见错误:
new Blob([chunk]) → 主线程持续占用,UI 冻结URL.revokeObjectURL 过早释放中间分片 URL → 合并时 Blob 数据丢失很多人用 URL.createObjectURL 后立刻 a.click(),但此时流可能还没写完。正确做法是监听 writable.getWriter().closed Promise,或在 WritableStream 的 close() 回调中执行下载逻辑。对于 Worker 方案,则要等所有分片 Blob 构造完成且合并完毕后再调用 createObjectURL。
容易被忽略的细节:
JSON.parse 整体 —— 改用 NDJSON 流式解析,或用 Response.body.getReader() 逐段读取revokeObjectURL,还得中断流的 controller.error() 或终止 WorkerBlob 的 type 必须显式设为 'text/csv;charset=utf-8',否则 Excel 可能误判编码,中文变乱码