对比理解 multipart/form-data 和 x-www-form-urlencoded 两个请求header
发请求过程中,我们常常会涉及到文件上传,上传文件的请求我们都会写,无非就是:
// 1. 新建 FormData 对象 (formData)
const formData = new FormData
// 2. 设置文件参数
formData.append('file', File)
// 3. formData 塞到请求body里去,peace (fetch 为例)
fetch('http://path/to/upload/dest.dev', {
method: "POST",
body: formData
})
但这是为什么呢,为什么这么写就能上传文件呢,为什么传别的参数不是这么做呢 (比如有的参数就是手动设置请求头 ContentType=x-www-form-urlencoded)
一开始在需求简单的时候,浏览器还未支持文件上传,这时web app和服务器 http 通信都是用的 x-www-form-urlencoded的形式(当然还包括其他一些类型的头,如 application/json 或者直接用 query)
后来为了增强 web app 的能力,让 http可以发送文件到服务器,增加了新的 Content-Type 类型来支持文件上传,这就是 multipart/form-data 相关RFC
我们通过实际两种方式,向服务器发送不同的数据内容来看看两者之间的差异
使用 x-www-form-urlencoded
用 postman 向服务器发送 myName=John Snow-😁 可以看到,在这个 body 类型中,非字母数字的部分都被转码了,我想这也是为什么这种 ContentType 叫做 urlencoded 的原因

使用 multipart/form-data
用 postman 向服务器发送多个数据,第一个是myName=John Snow-😁 的参数对,第二个是一个名为 file, 内容为一个二进制文件的参数对

可以看到, multipart/form-data 是一种根据分割线 来分割参数列表的内容类型协议, ContentType 请求头已经声明了使用 --------------------------621815209943923031726170 作为分割线(术语叫 boundary),而分割线隔开的内容就是各个参数部分的元信息和参数内容
那 FormData 对象又是怎么回事
FormData 对象做了什么
FormData 对象其实是为了简化构造符合 multipart/form-data 类型参数的辅助对象,屏蔽了参数拼接过程,不需要我们手动生成 boundary, 也不需要我们手动去拼接参数的各个部分,只需要实例化 FormData 对象,然后调用 append(key, value) 方法来加入各个参数 part 就行,上传时把 formData 对象传入 body, 格式转换和参数形式处理的问题它来自动处理, 并且当我们传入 body 的参数为 FormData 对象时,ContentType 头也会自动变成 multipart/form-data
例如上面的例子,我们可以用 FormData 对象编程式地调用:
const formData = new FormData
formData.append('myName', 'myName=John Snow-😁')
formData.append('file', myFile)
// 然后 formData 传入 body 发送即可
FormData 对象会为我们生成上图中那样的复杂参数结构