先记住这个答案
File 接口继承 Blob,并增加 name、lastModified、webkitRelativePath 属性。Blob 只表示不可变二进制数据,无文件名;File 在承载字节之外附带文件元数据,通常来自用户选择的文件或拖拽,也可用 new File() 由脚本构造。因此,多数接受 Blob 的 API(如 FileReader、URL.createObjectURL、fetch 的 body)也接受 File。但若需读取文件名或修改时间,必须将其当作 File;否则可只按 Blob 处理。
- File 继承 Blob,是带文件名和修改时间的 Blob。
- 接受 Blob 的 API 基本都接受 File,但反之不然。
- 需要元数据或用户文件语义时,不能降级为普通 Blob。
原型链与构造差异
在浏览器中,File 接口直接继承自 Blob,因此 file instanceof Blob 为 true。这意味着任何接受 Blob 作为参数的 Web API,如 FileReader、URL.createObjectURL()、fetch() 的 body 选项,都能直接接收 File 对象。但 Blob 构造函数无法生成 File,你可以通过 new File([blob], name, options) 从任意 Blob 创建 File。
二者显著差异在 type 和尺寸等 Blob 属性之外,File 还带文件源信息:name、lastModified(毫秒时间戳)以及 webkitRelativePath(当从目录上传时)。当浏览器从 <input type=file> 或拖拽生成对象时,它一定是 File;而用户脚本合成二进制时用 new Blob() 得到普通 Blob。判断代码时,先看数据是否需要文件语义:若只关心字节,可用 Blob;若需要文件名或最后修改时间,就必须处理 File。
图片预览与上传:File 不可丢的场景
一个常见页面:用户通过 <input type="file" accept="image/*"> 选择图片,页面用 URL.createObjectURL(file) 预览,随后用 fetch 上传。仅把 file 传给这些 API 不会丢失任何元数据,对象仍是 File;真正的降级发生在用旧内容生成新对象时。假设你构造 const blob = file.slice(0, file.size),则得到的 blob 是 Blob,其 name 为 undefined,上传时后端收到无名文件。这是最常见的降级场景。
正确处理是:需要生成分片或变换内容时,先保留原 File 对象的元数据。例如,若用 file.slice() 做分片上传,每个分片是 Blob,但上传时应在 FormData 中附加原文件名和最后修改时间的字段,或重新构造 new File([分片], file.name, { type: file.type, lastModified: file.lastModified }) 以保留语义。若用 canvas.toBlob() 生成压缩图,得到 Blob,必要时应包装为 File 再上传。否则后端只能收到浏览器给的默认文件名,无法据此识别扩展名或按原始文件名归档。总之,需要文件元数据的任何环节,都不应按纯 Blob 处理。
不要误判 File 与 Blob 的边界
需要注意,某些操作会返回 Blob 而非 File,比如 Blob.slice() 总是返回 Blob,不管原对象是否为 File。因此,如果你从 File 中切出一段并期望它仍是 File,则会丢失 name 等属性。类似地,canvas.toBlob() 的回传也是 Blob,不是 File。在这些分支处,显式检查 value instanceof File 能避免类型误判。
另一个边界是:FileReader.readAsArrayBuffer(file) 或 file.text() 返回结果不携带类型。你无法从 ArrayBuffer 或字符串反向推断出原对象类型,因此若需保留文件元数据,应在进入异步读取之前,把 File 引用和解析结果配套存储,或重建 File。反之,如果不是文件来源而是程序生成的业务数据,使用普通 Blob 即可,不必强行为其命名。除非处理文件上传或展示用户文件,否则 File 的额外字段并无必要,这就是“何时不能只按 Blob 处理”的判断准则。
容易答错的地方
- 误以为 Blob 就是 File
- Blob 是通用二进制容器,File 才是带文件信息的子类。例如
new Blob([data])不是 File,没有name与lastModified。只有用户选择的文件、或显式用new File(...)构造,才可能具备文件语义。 - 对 slice 后结果掉以轻心
- 调用
file.slice(start, end)产生的是 Blob,不是 File。若之后用 FormData 上传此分片,将失去文件名。正确做法是手动附加原名与修改时间,或使用带参数的new File()重新包装。
面试官还会怎么问?
为什么说 `file instanceof File` 始终是真,但 `blob instanceof File` 不一定?
因为 File 继承 Blob,File 实例也是 Blob 实例。而普通 Blob 不是 File。构造函数继承链决定了 instanceof 的结果。你可以用 obj instanceof File 判断是否包含文件元数据。
何时需要把 blob 包装成 File?
当需要获得文件名或计划用该系统文件语境(如上传、下载时带文件名、或需要 lastModified 参与缓存)时。例如从 canvas 导出的 blob,需要下载时名称为 'image.png',就可转换为 File。若只需 post 二进制内容,可直接 blob。
FileReader 接受 Blob,那 File 也能直接用?
能。FileReader.readAsDataURL(file) 与 readAsArrayBuffer(file) 都会正确处理 File,因为 File 是 Blob。FileReader 只读取字节,不需要文件名,这正是按 Blob 对待的合法场景。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。