Ajax向服务器发送请求时,数据类型主要由HTTP请求体(Body)的Content-Type决定,最常用的三种是表单类型(application/x-www-form-urlencoded)、JSON类型(application/json)和文件上传类型(multipart/form-data)。选择哪种数据类型,取决于你的后端接口约定和前端业务场景,下面我会从实际开发角度,带你把这三种类型掰开揉碎讲清楚,并配上传参代码和常见坑点。
为什么数据类型会直接影响请求成败
很多初学Ajax的朋友都有过这样的经历:后端同事甩过来一个接口文档,上面写着”参数格式:JSON”,结果你用默认的URL编码方式传参,接口返回400,又或者后端说”用FormData”,你强行转成JSON字符串,结果签名校验一直失败。
核心原因在于:HTTP协议本身不关心你传的是”字符串”还是”对象”,它只认字节流。 服务器拿到请求后,会先看Content-Type这个请求头,再决定用哪种解析方式去处理请求体,你前端设置的Content-Type与后端解析器不匹配,等于鸡同鸭讲。
Ajax并没有”自动识别数据类型”的超能力,它只是按照你指定的Content-Type,把JavaScript对象序列化成对应的文本格式,下面我们从最常见的三种格式逐个聊。
第一种:application/x-www-form-urlencoded(传统表单类型)
这是最早、最通用的请求数据格式,大部分Web框架默认支持它,用jQuery的$.ajax或者原生XMLHttpRequest时,如果没有特殊指定,浏览器通常默认采用这种类型。
实际长什么样
这种格式的本质是键值对通过&连接,键和值都做URL编码,中间用分隔。
name=%E5%BC%A0%E4%B8%89&age=18&city=%E5%8C%97%E4%BA%AC
其中%E5%BC%A0%E4%B8%89是”张三”的URL编码结果,这种格式也是Google搜索地址栏里?q=关键词的编码方式,相信你一定不陌生。
什么时候选它
- 后端接口使用Spring MVC且没有标注
@RequestBody,用@RequestParam接收参数 - 后端使用PHP原生
$_POST全局变量获取数据 - 做表单提交类需求,比如登录、搜索、简单的意见反馈
- 接口要兼容老系统,对方只处理传统表单格式
原生JavaScript实现方式
// 方式1:URLSearchParams(现代浏览器推荐)
const data = new URLSearchParams();
data.append('name', '张三');
data.append('age', 18);
fetch('/api/user/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data.toString() // 转成 name=%E5%BC%A0%E4%B8%89&age=18
});
注意: URLSearchParams.toString()会自动完成URL编码,如果你的某个参数值里包含特殊字符,比如&或,用它处理是最稳妥的。
这个类型的坑
最大的坑是嵌套对象没法表达,比如你要传一个数组hobbies: ['足球', '篮球'],用这种格式只能写成hobbies=足球&hobbies=篮球,后端拿到的是一个字符串列表,如果后端解析器不支持同名参数合并,你会只取到第一个值,此时可以换用hobbies[]=足球&hobbies[]=篮球这种写法,但后端得做特殊适配,所以遇到复杂结构,优先考虑JSON。
第二种:application/json(JSON类型)
当前前后端分离项目的绝对主流格式。 几乎可以说,只要是2020年之后新建的项目,后端接口十有八九让你传JSON,它的特点很直接:把JavaScript对象直接序列化成JSON字符串放在请求体里
。
实际长什么样
{"name":"张三","age":18,"hobbies":["足球","篮球"],"address":{"city":"北京","district":"海淀"}}
与表单格式相比,它的表达能力强很多:支持嵌套对象、数组、数字、布尔值,甚至空值null,后端会用Jackson、Gson或Fastjson这类库把它反序列化成Java对象。
什么时候选它
- 后端接口用
@RequestBody接收参数(Spring MVC中很常见) - 传参结构复杂:有嵌套对象、对象数组
- 前端现代化的框架工程(Vue、React项目大多用Axios,默认发送JSON)
- 需要表达”字段为空”的语义,发送
null而不是空字符串
原生JavaScript实现方式
fetch('/api/user/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: '张三',
age: 18,
hobbies: ['足球', '篮球'],
address: {
city: '北京',
district: '海淀'
}
})
});
多少次调接口失败的现场,问题往往出在JSON.stringify这一步。 比如你传了一个undefined值给JSON.stringify,对应的字段会被忽略;但如果你传的是NaN或者Infinity,它们会被序列化成null,这些细节会影响后端拿到的数据。
这个类型的坑
第一个坑是后端接口要求表单格式,前端却发了JSON,报错信息如Required request body is missing,尤其是GET请求带参数却用了JSON,第二个坑是JSON.stringify序列化Date对象,会变成ISO字符串,与后端期望的时间戳格式不匹配,第三个坑是内容里的中文,JSON格式本身是UTF-8编码,请求头里必须带上charset=UTF-8,否则某些老版本Tomcat会解析乱码。
行业内还有一个老生常谈的问题:GET请求不要带JSON body,虽然HTTP规范允许,但某些浏览器或代理服务器会丢弃GET请求的body,行业共识认为,GET请求的语义就是查询,参数应放在URL查询字符串中,如果参数结构复杂,那就改用POST。
第三种:multipart/form-data(文件上传类型)
当你要上传图片、PDF、Excel文件,或者同时提交文件和普通表单字段时,必须用这种类型,它的编码方式和前两种完全不同:不用URL编码,而是在请求体里按边界标记划分多个区块。
实际长什么样
它的报文太长,这里只示意核心结构:
----------------------------1234567890
Content-Disposition: form-data; name="file"; filename="demo.png"
Content-Type: image/png
[PNG文件二进制数据]
----------------------------1234567890
Content-Disposition: form-data; name="remark"
这是一张截图
----------------------------1234567890--
可以看到每个区块用分界线boundary分隔,浏览器会自动生成这个分界线字符串,这也是为什么使用FormData对象时,不需要手动设置Content-Type头,因为浏览器会带上multipart/form-data; boundary=----xxx完整信息,如果你手动强制设置了不带boundary的Content-Type,请求就会失败。
什么时候选它
- 上传用户头像、商品主图、身份证照片
- 导入Excel批量创建数据
- 同时提交数据内容和文件,发表带图片的帖子”
- 文件体积较大,超过几MB时(这类格式流的传输效率更高)
原生JavaScript实现方式
const formData = new FormData();
formData.append('remark', '这是一张截图');
formData.append('file', fileInput.files[0]);
fetch('/api/upload', {
method: 'POST',
// 不需要手动设置 Content-Type!
body: formData
});
请牢记:不要手动给FormData请求设置Content-Type: multipart/form-data,设置这个长长的边界字符串需要你自己算,且一旦算错服务器就无法解析,浏览器会自动处理,你只需要关注body传FormData即可。
这个类型的坑
很多上传失败是被反向代理服务器限制的。Nginx默认的client_max_body_size只有1MB,超过这个大小的文件会被拒绝并返回413 Request Entity Too Large,此时要在Nginx配置里调大这个值,或者拆分上传、后端做分片处理。
如果你在服务端渲染(如Express、Koa)中接收文件,需要用multer或busboy这类中间件,body-parser不解析multipart格式,可见格式选错,可能连中间件都能给你绊一跤。
四种主流Ajax库的数据类型设置对比
| 库/方式 | 表单类型传参 | JSON类型传参 | 文件上传传参 |
|---|---|---|---|
| 原生fetch | body里放URLSearchParams | body放JSON.stringify结果,手动加header | body放new FormData() |
| 原生XMLHttpRequest | setRequestHeader后send字符串 | setRequestHeader后send字符串 | send(new FormData()) |
| Axios | data放URLSearchParams,或让qs.stringify | data放对象,默认自动JSON序列化 | data放FormData,自动移除Content-Type |
| jQuery Ajax | data放对象,默认拼成表单格式 | data放JSON.stringify结果,并设置contentType | processData:false,data放FormData |
这个表格里最常用到的场景是Axios发送JSON数据的默认行为,在Axios中,如果你data传一个普通对象,它内部会自动调用JSON.stringify,并设置Content-Type: application/json,此时如果你强制给data设置了URLSearchParams,Axios也会自动覆盖成表单类型。
如何快速判断后端接口需要什么数据类型
除了查看接口文档,你还有三种快速判断的办法:
- 看请求头预期:如果后端文档里说”Content-Type: application/json”,直接JSON,如果说”表单”,直接用URLSearchParams或FormData。
- 看参数结构:参数是扁平化键值对且无嵌套,优先表单;有嵌套对象或数组,选JSON;含文件,必须用multipart。
- 调试观察:浏览器开发者工具的Network面板,点击请求名称查看”请求标头”和”负载”,能看清Content-Type和请求体长什么样,如果后端返回415错误(Unsupported Media Type),大概率是Content-Type与预期不符。
业内专家指出,后端接口设计时最常见的做法是:查询操作尽量用GET+查询参数,简单提交用表单类型,复杂业务结构和文件上传用JSON或multipart,你在前端对接接口前,先确认对方后台是什么语言写的Java Spring项目通常偏好JSON,PHP和Go部分项目偏好表单,心里有个谱可以减少来回沟通成本。
前端实战场:从表单到JSON的迁移场景
假设你现在接管一个老项目,原来用jQuery发送表单类型请求:
$.ajax({
url: '/api/user/update',
type: 'POST',
data: { name: '王五', age: 22 },
success: function(res) { ... }
});
后来后端升级,要求改成JSON格式,新版写法是:
$.ajax({
url: '/api/user/update',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ name: '王五', age: 22 }),
success: function(res) { ... }
});
改动量是两行代码:加contentType,用JSON.stringify包住data,但别小看这两行很多生产事故恰恰是迁移过程中,有的接口改JSON,有的没改,前端访问时顺序错乱导致部分页面报错400,建议做一个工具函数统一封装,把数据格式判断放在函数内部。
遇到跨域时的数据类型选择
跨域请求(CORS)对Content-Type有额外限制,浏览器在发起跨域POST请求时,如果Content-Type不是application/x-www-form-urlencoded、multipart/form-data或text/plain这三种简单类型,会先发送一次OPTIONS预检请求,这意味着:
- 用JSON格式发跨域请求,会多一次预检往返
- 服务器如果没按CORS规则返回相应头,你的Preflight就失败了,后续POST根本发不出去
- 临时调试时,把JSON改成表单格式,能绕过预检(但这不是正规解法)
正规解法是后端在响应中允许对应Content-Type,比如Access-Control-Allow-Headers: Content-Type,据国内各大云厂商的网关配置文档显示,生产环境建议在网关层统一配置CORS白名单,而不是让每个应用自己解决。
常见问题解答
发送Ajax请求时,Content-Type设为text/plain会怎样?
服务器会按纯文本解析请求体。它本质上不是任何一种标准结构化格式,后端框架不会自动帮你反序列化,除非你有自定义解析逻辑,否则绝大多数后端接口无法处理这种类型,它常见于WebSocket传输或某些日志上报接口,不推荐用于普通业务数据提交。
Ajax上传文件时,FormData和FileReader.readAsDataURL有什么区别?
FormData直接发送二进制文件流,使用multipart格式,是向后端传文件最正统的方式,而FileReader.readAsDataURL把文件转成Base64字符串,可以通过JSON发送,区别在于:FormData更节省流量,Base64会增大体积约33%,且后端接收Base64还需要解码,多一步处理,行业共识是上传大文件必须用FormData,Base64只适合预览或极小的图片。
如何判断后端返回的数据是JSON还是其他格式?
在Ajax的success回调或then回调中,可以检查responseText的第一个字符,如果是或[,再尝试JSON.parse。你不需要自己写这个逻辑,因为jQuery会自动根据响应头解析,Axios也会自动做JSON转换,如果发现返回的字符串没有被解析成对象,考虑后端响应头是否漏了Content-Type: application/json。
无论你的项目基于Vue、React还是传统jQuery,最稳妥的做法是在项目入口处封装统一的请求工具,把Content-Type和body序列化逻辑集中管理,避免每个页面对自己的数据类型负责,这样一旦后端接口格式调整,你只需要改一处,就能应对全站所有Ajax请求,数据类型的核心原则一句话:后端能解析的,才是对的数据类型;前端能明确的,才是稳的请求体。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/734930.html





