file.slice()是唯一可控的分段入口,因其返回新blob、不污染原file对象,且支持字节级精确切片;需配合math.min防越界、1–5mb合理分片、及时释放filereader及限流并发,才能避免内存爆炸。

直接用 <form></form> 提交大文件或调用 FormData.append("files", input.files) 会把全部 File 对象及其元数据一次性加载进内存,极易触发 OOM —— 这不是后端配置问题,是前端没做分段控制就交出去了。
为什么 file.slice() 是唯一可控的分段入口
浏览器对 input.files 的引用是强持有,只要不手动释放 FileReader 实例、不切断闭包引用,每个 File 就一直卡在堆里。而 file.slice(start, end) 返回的是新 Blob,不污染原对象,且可精确控制每次读取字节数。
- start/end 必须是字节偏移量,别用字符长度或行号;最后一片要用
Math.min(start + chunkSize, file.size)防越界 - 每片大小建议 1–5 MB:太小导致 HTTP 开销占比高;太大单次
FileReader.readAsArrayBuffer()容易卡死 UI 并拖慢 GC - 读完立刻设
reader = null,否则多个并发读会堆积 Detached DOM tree 和 Blob 引用,V8 不敢回收 - 别用
readAsDataURL,Base64 编码会让内存占用翻 1.3 倍以上
怎么避免 FileReader 和 fetch 并发压垮内存
并发开 10 个 fetch 请求,Chrome 默认同域只放行 6 个连接,其余排队或被 cancel,反而放大失败率和重试成本。真正要控的是“同时活跃的 FileReader 实例数”和“未 resolve 的 Promise 数”。
- 用
async/await+ 循环控制并发数,例如每次最多启动 3 个上传任务 - 每个请求必须带
Content-Range头(如bytes 0-5242879/10485759),服务端靠它定位写入位置,否则多片并发写入会覆盖错位 - 失败时只重试当前分片,不要全量回滚——这是断点续传的基础,也是降低重传内存压力的关键
- 禁用
Promise.all,改用Promise.allSettled或队列模式,避免一个失败阻塞整批
哪些操作看似省事实则引爆内存
很多写法在小文件下没问题,一旦文件超 100MB 就立刻暴露问题。这些坑不是报错,而是静默吃内存,直到页面卡死或崩溃。
-
Array.from(input.files):哪怕你只打算发个文件名列表,这句也会强制创建新数组并拷贝全部File引用 -
innerHTML += <div>...</div>:高频拼接字符串再赋值,会反复销毁重建子树,旧节点无法及时 GC - 给每个列表项单独绑定
addEventListener:10 万个div绑 10 万个监听器,内存轻松破 80 MB - 把整个
response.data传进闭包(比如 ECharts 的label.formatter):捕获的是响应体全部字段,不是你需要的那几个
真正麻烦的从来不是切片本身,而是你是否在读完立刻释放 FileReader、是否用 Content-Range 精准标记每片位置、是否让失败只影响当前片——这些细节不处理,分片就只是把大炸弹拆成一堆小炸弹,还是在同一时间引爆。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











