前端大坑!文件切片上传后端总报错找不到文件名?

前两天同事踩了个特别离谱的坑,折腾大半天没搞明白。

他做大文件上传功能,用 input 文件选择框拿到本地文件,然后用 slice 方法给文件做分片,准备搞分片上传。结果把切好的文件片段传给后端,后端一直报错:找不到文件名

他当场懵了:原始文件明明有名字,后缀也齐全,怎么一切片,文件名直接凭空消失了?

其实根本原因特别简单:他压根没分清 JS 里的 Blob 和 File。很多前端老手都在这里栽过跟头,今天用大白话给你讲透,以后再也不踩这个坑。

一、先搞懂:Blob 到底是个啥?

你别记那些专业术语,直接把 Blob 理解成一个纯二进制收纳桶

它只管装图片、视频、文本、文件这类二进制数据,别的啥都不管,只记两个信息:

  1. size:数据占多大内存
  2. type:文件的格式类型,比如图片、文本、JSON

它就只是单纯的一堆二进制数据,没有名字、没有修改时间,光秃秃一个数据包。

随便写个简单例子,就能造出一个 Blob:

js 复制代码
// 把普通文本塞进二进制桶里 
const textData = '前端踩坑:Blob和File别搞混';
const myBlob = new Blob([textData], { type: 'text/plain' });
console.log(myBlob.size); // 占用大小 
console.log(myBlob.type); // 文件类型 
console.log(myBlob.name); // undefined 压根没有文件名!

平时我们用它最多的场景:接口拿图片二进制、生成图片预览链接、做文件下载、文件临时切片。

二、再搞懂:File 又是个啥?

一句话:File 就是带身份证的 Blob

它天生继承了 Blob 所有的能力,能用 Blob 的所有方法,但是多了两个关键身份信息:

  • name:实打实的文件名,比如风景.jpg、简历.pdf
  • lastModified:文件最后一次修改的时间戳

我们平时用 <input type="file"> 选本地文件、拖拽上传拿到的,全都是 File 对象

示例代码:

js 复制代码
// 文件选择框监听选文件 
document.querySelector('#fileInput').addEventListener('change', function(e) { 
// 拿到选中的本地文件,是标准 File 对象 
const file = e.target.files[0]; console.log(file.name); // 有文件名

console.log(file.size); // 文件大小 
console.log(file.type); // 文件格式 
console.log(file.lastModified);// 修改时间戳 });

三、一张大白话分清两者区别

特点 Blob File
有没有文件名 没有 自带 name 文件名
有没有修改时间 没有 自带 lastModified
怎么生成 代码 new Blob 创建 本地选文件、拖拽文件,也能代码手动 new File
适合干啥 临时存二进制、切片、预览、下载 正式上传、要展示文件名、后端校验文件

记住一句核心:File 能当 Blob 用,但 Blob 绝对不能直接当 File 用,因为缺了文件名这些关键信息。

四、三个高频踩坑点,全是真实工作里常遇到的

坑一:文件切片后,文件名直接没了

绝大多数人都以为:我用 File 对象调用 slice 切片,切出来的还是 File。

大错特错!File 调用 slice 切出来的,永远是普通 Blob,直接把文件名给丢没了。

js 复制代码
// 监听文件选择
document.querySelector('#file').onchange = function (e) {
  // 拿到 File(有名字)
  const file = e.target.files[0];
  console.log('原文件名字:', file.name); // 正常显示:xxx.png

  // 切片!!!重点:slice 返回的是 Blob,不是 File!
  const chunk = file.slice(0, 1024 * 1024);

  // 坑来了:切片后名字没了
  console.log('切片后的名字:', chunk.name); // undefined!!!
};

这就是同事遇到的问题:把无名字的 Blob 分片传给后端,后端匹配不到文件名,自然报错合并失败。

解决办法:把切出来的 Blob,重新包装成带文件名的 File 再上传。

js 复制代码
const file = e.target.files[0];
const chunk = file.slice(0, 1024 * 1024);

// 把 Blob 重新包装成 File,把名字加回去
const realChunk = new File([chunk], file.name, {
  type: file.type,
  lastModified: file.lastModified
});

console.log(realChunk.name); // 正常显示!

坑二:直接拿 Blob 上传,后端不认

很多人图省事,直接把 Blob 塞进 FormData 传给后端。

你会发现后端收到的文件,名字变成了 blob 或者 blob.bin,没有后缀。后端如果按后缀校验文件类型(只允许 jpg、png、pdf),直接就给拦截报错。

js 复制代码
// 假设你从接口拿到了图片 Blob
const blob = await fetch('https://picsum.photos/200').then(res => res.blob());

const formData = new FormData();
formData.append('file', blob); // ❌ 直接传 Blob

// 后端收到:文件名为空 / blob / blob.bin,没有后缀
fetch('/upload', { method: 'POST', body: formData });

解决办法:上传前手动给 Blob 包一层 File,自定义文件名和后缀。

js 复制代码
const blob = await fetch('https://picsum.photos/200').then(res => res.blob());

// ✅ 手动给 Blob 加上文件名和后缀
const file = new File([blob], 'user-avatar.jpg', { type: 'image/jpeg' });

const formData = new FormData();
formData.append('file', file); // 后端能拿到正确文件名

fetch('/upload', { method: 'POST', body: formData });

坑三:图片预览链接不释放,页面越用越卡

用 Blob / File 生成本地预览链接很方便,但很多人只创建不销毁。

链接占用的内存不会自动释放,页面打开多了,内存越堆越高,变得卡顿甚至卡死。

js 复制代码
const file = e.target.files[0];
const url = URL.createObjectURL(file);

document.querySelector('#img').src = url;

// ❌ 用完不释放,内存一直占用

解决办法:图片加载完成后,立马释放临时链接。

js 复制代码
const file = e.target.files[0];
const url = URL.createObjectURL(file);
const img = document.querySelector('#img');

img.src = url;

// ✅ 图片加载完成后,立刻释放内存
img.onload = function () {
  URL.revokeObjectURL(url);
  console.log('预览链接已释放,不占内存啦');
};

五、什么时候该用 Blob?什么时候该用 File?

用 Blob 就行的场景

  • 接口返回图片二进制,生成页面预览
  • 前端生成文本、JSON,触发浏览器下载
  • 文件分片时临时存放切片数据
  • Canvas 导出图片二进制数据

必须用 File 的场景

  • 用户本地选择文件、拖拽文件上传
  • 需要在页面展示文件名、文件大小
  • 后端需要校验文件名、文件后缀
  • 分片上传要给每块分片绑定原文件信息

Blob 和 File 互相转换,超简单

1、Blob 转 File:给二进制包个文件名外壳

js 复制代码
// 已有一个无名字的 Blob
const blob = new Blob(['测试内容'], { type: 'text/plain' });
// 包装成带名字的 File
const file = new File([blob], '测试文档.txt', { type: 'text/plain' });

console.log(file.name); // 测试文档.txt

2、File 转 Blob:直接切片就行

js 复制代码
const file = e.target.files[0];
// 整文件转成 Blob
const blob = file.slice(0, file.size);

实用完整版:文件分片上传正确写法

给你换了一套简洁好懂的业务可用代码,避开文件名丢失的坑:

js 复制代码
// 分片上传主方法
async function handleFileUpload(file) {
  // 每片大小 500KB
  const chunkSize = 500 * 1024;
  // 计算总片数
  const total = Math.ceil(file.size / chunkSize);

  // 循环切分每一片
  for (let i = 0; i < total; i++) {
    const start = i * chunkSize;
    const end = start + chunkSize;
    // 切片得到 Blob
    const chunkBlob = file.slice(start, end);

    // 关键:把 Blob 重新包装成 File,保留原文件名
    const chunkFile = new File([chunkBlob], file.name, {
      type: file.type,
      lastModified: file.lastModified
    });

    // 组装表单数据
    const formData = new FormData();
    formData.append('chunkFile', chunkFile);
    formData.append('chunkIndex', i);
    formData.append('totalChunk', total);
    formData.append('fileName', file.name);

    // 传给后端
    await fetch('/api/uploadChunk', {
      method: 'POST',
      body: formData
    });
  }
}

最后总结几句大白话

  1. Blob 就是纯二进制数据桶,只有大小和类型,没有名字
  2. File 是带文件名、修改时间的升级版 Blob,本地选文件拿到的都是它;
  3. File 切片 slice 出来的一定是 Blob,别傻傻以为还是 File;
  4. 只要是正式上传、要文件名、后端校验,一律用 File;
  5. Blob 上传一定要手动包装成 File,给它配上文件名;
  6. 预览用的 ObjectURL,用完一定要手动释放,避免内存泄漏。

以后再做文件上传、分片上传,再也不会被文件名莫名其妙搞报错了。

相关推荐
Sylvia33.1 小时前
世界杯数据链路解析:从球场传感器到终端推送的毫秒级架构
java·前端·python·架构
镜宇秋霖丶1 小时前
2026.5.10@霖宇博客制作中遇见的问题
前端·vue.js·elementui
ai超级个体1 小时前
前端唯一的护城河?结合 AI 将字节组件库 Headless 化后的感想~
前端·react·ai编程·ant design·组件库·vibe coding
冴羽yayujs1 小时前
前端周报:Remix 3、Node 26 与 Chrome 148
前端
问心无愧05131 小时前
ctf show web 入门39
android·前端·笔记
卷无止境1 小时前
Alpine.js入门笔记
前端
@王先生11 小时前
【K8S-ETCD初始化三节点集群】
前端·chrome·k8s·etcd·集群
LinDaiDai_霖呆呆2 小时前
做 Agent 开发入门必懂的 10 个 Agent 核心概念
前端·agent·ai编程
小四的小六2 小时前
WebView 从0到1搭建线上性能监控体系
javascript·webview