如何用JavaScript在浏览器前端将文件拆分为40KB分片后上传
前端文件分片上传实现方案与优化建议
我之前做过不少前端分片上传的项目,先聊聊你当前用base64拆分的痛点——base64编码会让文件体积膨胀约33%,也就是说你设定的40KB base64分片,对应的原始二进制数据其实只有~30KB,这会白白增加上传的数据量和前端编码、后端解码的额外开销。如果没有特殊场景限制,优先推荐直接操作二进制Blob/ArrayBuffer分片,效率会高很多。下面给出具体实现方案和优化思路:
一、核心实现方案:二进制Blob分片(推荐)
直接利用浏览器原生的File.slice()方法切割文件为Blob分片,无需转base64,从根源避免体积膨胀和编码开销。
实现步骤
- 获取用户选择的文件,生成唯一文件标识(用于服务器合并分片)
- 按40KB的大小计算分片总数
- 循环切割文件为Blob分片,逐个/并发上传
- 所有分片上传完成后,通知服务器合并文件
代码示例
const CHUNK_SIZE = 40 * 1024; // 40KB 分片大小(字节) async function uploadFile(file) { const fileSize = file.size; const totalChunks = Math.ceil(fileSize / CHUNK_SIZE); // 生成唯一文件ID,避免同名文件冲突 const fileId = `${file.name}-${Date.now()}-${Math.random().toString(36).slice(2)}`; for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { const start = chunkIndex * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, fileSize); // 直接切割文件为Blob分片 const chunk = file.slice(start, end); // 构造FormData,和普通文件上传格式一致 const formData = new FormData(); formData.append('fileChunk', chunk); formData.append('fileId', fileId); formData.append('chunkIndex', chunkIndex); formData.append('totalChunks', totalChunks); formData.append('fileName', file.name); // 上传分片(这里用fetch示例,也可以用XMLHttpRequest) const response = await fetch('/api/upload-chunk', { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`第${chunkIndex + 1}个分片上传失败,请重试`); } } // 所有分片上传完成,请求服务器合并文件 const mergeResponse = await fetch('/api/merge-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileId, fileName: file.name, totalChunks }) }); if (mergeResponse.ok) { console.log('文件上传完成!'); // 这里可以添加成功提示或页面跳转逻辑 } } // 绑定文件选择事件 document.getElementById('file-input').addEventListener('change', (e) => { const selectedFile = e.target.files[0]; if (selectedFile) { uploadFile(selectedFile).catch(err => alert(err.message)); } });
二、若必须使用base64的优化方案
如果因为后端接口限制等原因必须用base64上传,可通过以下方式减少浪费:
- 计算对应原始二进制的分片大小:base64每3字节原始数据转4字符,所以40KB的base64对应的原始二进制大小为
40*1024 * 3/4 = 30720字节(30KB),按这个大小切割原始文件,转base64后刚好不超过40KB。 - 避免一次性转整个文件为base64:大文件一次性转base64会占用大量内存,应分片读取转码。
代码示例
const MAX_BASE64_CHUNK_SIZE = 40 * 1024; // 目标base64分片大小 const RAW_CHUNK_SIZE = Math.floor(MAX_BASE64_CHUNK_SIZE * 3 / 4); // 对应原始二进制大小 async function uploadFileWithBase64(file) { const fileSize = file.size; const totalChunks = Math.ceil(fileSize / RAW_CHUNK_SIZE); const fileId = `${file.name}-${Date.now()}-${Math.random().toString(36).slice(2)}`; for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { const start = chunkIndex * RAW_CHUNK_SIZE; const end = Math.min(start + RAW_CHUNK_SIZE, fileSize); const chunk = file.slice(start, end); // 分片转base64(去掉dataURL前缀) const base64Data = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result.split(',')[1]); reader.onerror = reject; reader.readAsDataURL(chunk); }); // 上传base64数据 const response = await fetch('/api/upload-base64-chunk', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileId, chunkIndex, totalChunks, fileName: file.name, base64Data }) }); if (!response.ok) { throw new Error(`第${chunkIndex + 1}个分片上传失败`); } } // 发起合并请求 const mergeResponse = await fetch('/api/merge-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileId, fileName: file.name, totalChunks }) }); if (mergeResponse.ok) { console.log('文件上传完成!'); } }
三、通用优化建议(提升体验与可靠性)
不管用哪种分片方式,这些优化都能大幅提升上传体验:
- 断点续传:上传前先请求服务器,获取当前文件已上传的分片索引,跳过已上传的分片(需要服务器记录每个
fileId对应的已上传分片)。 - 并发上传:不要串行上传,可控制同时上传3-5个分片(用
Promise.all限制并发数),提升上传速度,注意不要超过服务器的并发限制。 - 进度跟踪:监听每个分片的上传进度,计算整体上传进度(已上传分片大小总和/文件总大小),实时展示给用户。
- 分片校验:每个分片计算MD5/SHA1哈希,上传时一起发送给服务器,服务器验证分片完整性,避免损坏。
- 错误重试:分片上传失败时自动重试3次,仍失败再提示用户手动重试。
- 大文件内存优化:对于GB级大文件,始终用
File.slice()流式处理,避免一次性读取过多数据导致内存溢出。
内容的提问来源于stack exchange,提问作者Patoshi パトシ
相关产品推荐
相关产品推荐

