progress事件更优:浏览器原生调度、零额外内存分配、弱网下仍可反馈增量;ReadableStream需手动读取累加、创建Uint8Array引发GC压力、依赖Content-Length且合并开销O(n)。

两者都能实现下载进度计算,但底层机制和实际开销不同:progress 事件是浏览器原生调度的轻量回调,ReadableStream 则需要手动读取、累加、解析字节流,带来额外的内存与 CPU 开销。
progress 事件:低开销、高响应性
XMLHttpRequest 的 upload.onprogress 和 download.onprogress 是浏览器内核直接触发的事件。当网络栈收到新数据块时,引擎自动计算 e.loaded 和 e.total,并同步派发事件——整个过程不经过 JavaScript 主线程调度,也不产生中间 Uint8Array 对象。
- 无需手动管理 chunk 数组或拼接逻辑
- 不额外分配内存(如
Uint8Array、Array.push()) - 即使总大小未知(
e.lengthComputable === false),也能反馈相对增量 - 在弱网或高吞吐场景下,事件频率受浏览器节流,但整体 CPU 占用极低
ReadableStream:可控但有代价
Fetch 借助 response.body.getReader() 暴露底层流,进度计算依赖开发者主动调用 reader.read() 并维护 receivedLength。每次 read() 返回一个 Uint8Array,需手动累加长度、更新 UI,并最终合并为完整数据。
- 每块数据都会创建新的
Uint8Array实例,频繁 GC 可能引发卡顿(尤其在千兆带宽或大文件场景) - 必须依赖
Content-Length头才能算百分比;若服务未返回该头,就只能显示“已接收 X 字节”,无法换算百分比 -
while(true)循环 +await reader.read()属于微任务链,大量小 chunk 会增加事件循环负担 - 合并 chunk 需额外遍历(
chunksAll.set(chunk, position)),时间复杂度 O(n),对百 MB 级文件明显可感
关键性能分水岭
差异在中小文件(
立即学习“Java免费学习笔记(深入)”;
- 连续高速下载(如视频片段、大型 CSV):ReadableStream 的内存分配节奏可能跟不上网络吞吐,导致主线程抖动
- 低端移动设备:V8 堆内存紧张时,频繁创建/销毁 Uint8Array 易触发 GC 暂停
- 服务端未设
Content-Length:progress 仍可报告增量,ReadableStream 则失去百分比能力,仅能做粗略估算
不是非此即彼的选择
如果只需简单进度条且服务支持 CORS + Content-Length,Fetch + ReadableStream 足够;但若追求稳定、兼容、低资源占用,尤其面向企业级文件传输或旧安卓 WebView,XMLHttpRequest 的 progress 仍是更省心的方案。Axios 内部封装了 XHR,也继承了这一优势,而它对 Fetch 的适配则需额外注入流处理逻辑。



















