直接用 提交大批量文件会导致内存溢出,因浏览器缓存所有 file 对象及其元数据;应按大小分片处理,小文件分批提交,大文件分片上传并限流,且需在 change 事件中用 subtlecrypto 生成唯一 id 缓存。

直接用 <input type="file" multiple> 提交大批量文件,浏览器会在内存中缓存所有 File 对象及其元数据,哪怕你只打算发个文件名列表——一旦选中 100 个 50MB 的视频,光 FileList 就可能吃掉 2GB+ 内存,页面卡死或直接 OOM。这不是后端的问题,是前端没做分段控制就交出去了。
为什么不能直接用 FormData.append("files", input.files)
很多人以为 FormData.append("files", input.files) 是“批量上传”的捷径,其实它只是把整个 FileList 塞进一个字段,浏览器仍会为每个 File 对象保留完整引用(包括 lastModified、webkitRelativePath 等),且后续调用 fetch 或 XMLHttpRequest 时,底层仍需逐个读取 blob 内容。更糟的是:一旦你用 FileReader 预览或校验,整个文件内容就被加载进内存——500MB 文件 = 至少 500MB ArrayBuffer。
- 别在用户点击上传前就调用
Array.from(input.files),除非你确定这批文件总大小 -
input.files是只读类数组,不能splice或filter后再传给 FormData;必须手动 slice 出子集 - 服务端收到的 multipart body 里,每个
file字段仍是独立 part,但前端已无权控制传输顺序或中断重试
如何对 FileList 做前端分段控制(非分片)
所谓“内存分段”,是指不一次性加载全部文件,而是按批次处理:每次只取 N 个文件构造 FormData 并发送,等这批响应完成后再取下一批。这能压住内存峰值,也便于失败重试和进度反馈。
- 用
for (let i = 0; i 循环,每次取 <code>files.slice(i, i + batchSize),注意slice返回新数组,不污染原FileList -
batchSize推荐设为 3–5,不是越大越好:单次请求太大,服务端可能拒绝(如 nginx 默认client_max_body_size 10M),且失败后整批重传成本高 - 每批用独立
FormData实例,append("file", file)单个追加,别用append("files", fileList) - 用
Promise.allSettled(batchPromises)而非Promise.all,避免一个失败导致整批中断
大文件 + 大数量混合场景下的真实瓶颈在哪
当同时面对“单个文件超 1GB”和“一次选 200 个文件”时,问题本质是两层叠加:FileList 引用内存占用 + 单文件读取内存占用。这时候光靠“分批提交”不够,必须拆解为两个动作:先分批选文件,再对每个大文件做分片。
- 用户点击上传前,立刻遍历
input.files,用file.size过滤出 >100MB 的文件,单独标记为“需分片” - 对小文件(≤100MB)走分批 FormData 提交;对大文件,立即启动分片逻辑(
file.slice()+FileReader+ 并发限流) - 不要共用同一套进度条:小文件按“文件数”计进度,大文件按“已上传字节数 / 总字节数”计,否则视觉严重失真
-
sessionStorage里缓存每个文件的唯一 ID(基于分片哈希聚合生成),刷新后能续传;但File对象本身无法跨页面复用,ID 必须在change事件里立刻算好存进去
真正容易被忽略的点是:FileList 的生命周期比你想象中短。页面刷新后,input.files 清空,但如果你只存了文件名和 size 去生成 ID,服务端拿到的就只是“同名同大小”的假指纹——必须在用户选中瞬间,用 SubtleCrypto.digest() 对首尾几个分片哈希聚合,才算稳。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











