为什么上传图片要用 const formData = new FormData(),而不是 JSON 序列化?

93 阅读5分钟

为什么上传图片要用 const formData = new FormData(),而不是 JSON 序列化?

在开发网页应用时,上传图片是一个非常常见的需求。比如,用户要上传头像、分享照片,或者提交包含图片的文档。当你开始实现这个功能时,可能会遇到一个选择:是用 FormData 对象来发送数据,还是把图片数据塞进一个 JSON 对象里,然后用 JSON.stringify() 发送?

答案很明确:上传图片(或其他文件),应该使用 const formData = new FormData(),而不是 JSON 序列化。

1. 文件 vs. 文本

要理解为什么,首先要明白两种数据的本质区别:

  • JSON 是文本格式: JSON是一种轻量级的数据交换文本格式。它只能安全地表示字符串、数字、布尔值、对象、数组和 null。它的所有内容最终都是可读的字符
  • 图片是二进制数据: 一张 JPG、PNG 或 GIF 图片,本质上是一串由 0 和 1 组成的二进制数据流。这些数据包含了像素的颜色、压缩信息等,它们不是人类可读的文本,很多字节的值在文本中是非法或容易引起解析错误的。

所以你无法直接把一个二进制的图片,塞进一个只认文本的 JSON 里。

2. 若尝试用 JSON 上传图片

既然 JSON 只能处理文本,那有没有办法把二进制的图片“翻译”成文本呢?有,那就是 Base64 编码

  • Base64 是什么? 它是一种编码方式,能把任何二进制数据转换成一个只包含字母(A-Z, a-z)、数字(0-9)以及 +/= 符号的长字符串。这个字符串就是“文本”了,可以放进 JSON。
  • 过程如下:
    1. 用户选择一张图片。
    2. 前端 JavaScript 读取图片文件,将其转换成 Base64 字符串。
    3. 把这个 Base64 字符串放进一个 JSON 对象里。
    4. JSON.stringify() 序列化这个对象,通过网络发送给服务器。
    5. 服务器收到 JSON,解析它,取出 Base64 字符串。
    6. 服务器再把 Base64 字符串“解码”回原始的二进制图片数据,然后保存。

这在技术上完全可行。但它带来了巨大的问题:

  • **数据体积膨胀 ** Base64 编码会让原始数据大小增加约 1/3。一张 3MB 的照片,编码后变成约 4MB 的文本。这不仅浪费带宽,让上传更慢,还增加了用户的流量消耗。
  • 前端内存压力大 为了生成 Base64 字符串,浏览器需要先把整个图片文件读入内存。对于大文件,这可能导致页面卡顿甚至崩溃。
  • 服务器处理开销高 服务器不仅要解析 JSON,还要花费 CPU 时间去解码那个巨大的 Base64 字符串,才能得到真正的图片。这对服务器性能是一种不必要的负担。
  • 效率低下 整个过程绕了远路(进行了数据的转换),不够直接和高效。

所以,虽然能走通,但这条路又慢、又费资源

3.使用FormData

FormData 是浏览器提供的一个专门用来构造表单数据的对象。它天生就是为了处理像文件上传这种复杂场景而设计的。

它是怎么工作的?

当你创建 const formData = new FormData() 并使用 append() 方法添加文件时:

// 假设有一个文件输入框 <input type="file" id="imageInput">
const fileInput = document.getElementById('imageInput');
const formData = new FormData();
formData.append('image', fileInput.files[0]); // 直接附加文件对象!
formData.append('title', '我的风景照'); // 也可以同时附加其他文本信息
  • 直接处理文件: FormData 可以直接接收 FileBlob 对象,不需要你手动转换成文本。
  • 自动采用 multipart/form-data 格式: 当你用 fetch()XMLHttpRequest 发送 FormData 时,浏览器会自动将请求的 Content-Type 设置为 multipart/form-data,并按照这种格式打包数据。

multipart/form-data 格式

这种格式就像一个多层包裹:

  • 它把整个请求数据分成多个独立的“部分”(parts)。
  • 每个部分可以有自己的“标签”(字段名,如 imagetitle)和自己的“内容类型”(Content-Type)。
  • 文本部分(如 title)是普通的文本。
  • 文件部分(如 image)则以原始的二进制格式打包,完全避免了 Base64 编码!
  • 各部分之间用一串独特的“边界”(boundary)字符串隔开,确保服务器能准确地拆分它们。

FormData 的优势:

  1. 高效传输: 图片以原始二进制形式发送,没有 33% 的体积膨胀,上传速度最快,最节省流量。
  2. 内存友好: 浏览器在发送时可以更高效地处理大文件,不一定需要一次性全部加载到内存。
  3. 服务器处理简单: 几乎所有主流后端框架(Node.js, Python Django/Flask, Java Spring, PHP 等)都内置了对 multipart/form-data 请求的解析器。服务器能轻松地将文件部分提取出来直接保存,同时获取其他表单字段,处理逻辑非常直接。
  4. 符合标准: 这是 HTML 表单进行文件上传的标准方式。它从 <form enctype="multipart/form-data"> 的传统方式自然演化而来,兼容性好,是业界公认的最佳实践。
  5. 使用简单: API 设计直观,append() 方法就能搞定。

结论

传图片,就用 const formData = new FormData();

FormData 处理文件,用JSON处理纯文字信息。