最近一个做后端的小伙伴跑过来问我:“为什么我用 AJAX 请求,服务端收到的中文永远是一堆乱码?”我一看代码就明白了,他把数据放在 URL 后面直接拼,然后提交编码也没设置,服务端那边又是默认按 ISO-8859-1 解。这个问题看起来很小,但牵扯到 AJAX 请求的编码格式、参数赋值方式、Content-Type 设置,实际上是一整块知识。类似的疑问我几乎每年都能遇到几回,所以决定系统整理一篇以“AJAX 实例”为主线的内容,把从底层原理到真实项目里最常见的写法、问题和排查技巧都摊开来聊一遍。
这篇文章适合刚接触前后端交互的前端新人,也适合后端工程师想快速搞懂 AJAX 请求什么时候该用什么格式传参,还适合团队里维护老项目、需要处理拖了很久的 ajax 兼容问题的同学。我会从 XMLHttpRequest 讲到 fetch,从 GET 讲到 POST,从表单编码讲到 application/json,再到 Layui 这类框架里的封装怎么用,最后把编码、参数丢失、状态码、乱码这些“高频翻车现场”逐个拆开。
1. AJAX到底在做什么:一次请求的完整“旅行”
1.1 没有AJAX的年代,网页是怎么运作的
要理解 AJAX 到底解决了什么问题,可以先把时间往回拨一下。在 AJAX 这个概念还没普及的年代,网页要往服务器提交数据,最常见的方式就是提交一个表单。浏览器拿到表单数据以后,会发起一次完整的页面请求,服务器处理完再返回一个新的 HTML 页面,然后整个浏览器窗口从头刷新一遍。哪怕你只是想给文章点一个赞、更新一下购物车里的数量,页面也要整个跳转一次,体验上又慢又不连贯。
AJAX 的全称是 Asynchronous JavaScript And XML,翻译过来就是“异步 JavaScript 和 XML”。它的核心思路是让浏览器在后台偷偷发一个请求到服务器,服务器返回的数据到了以后,JavaScript 再把页面上的某一块内容动态更新掉。整个过程中页面不刷新,用户不需要等白屏,体验是连续的。现在几乎所有网站都在用这套交互方式,尤其是 SPA 应用,整个站点可能只有一份 HTML,剩下的内容全部靠 AJAX 请求数据然后通过 DOM 操作渲染出来。
很多同学会把 AJAX 当作一个具体的库或者某种框架,实际上它只是“浏览器提供的一整套异步网络请求能力”的总称。真正干活的对象,是浏览器环境里的 XMLHttpRequest 对象,或者后来出现的 fetch API。你用 jQuery 的 $.ajax、axios、Layui 的 $.get,底层走的还是浏览器这些原生能力,只是有些人帮你把代码封装得更简短了。
1.2 数据流转链路:请求、响应、DOM 与数据格式
我们在页面上最常见的 AJAX 流程可以拆成五个环节。第一步,JavaScript 代码里创建一个请求对象。第二步,通过请求对象把请求发到指定的 URL 地址,同时可以带上一些参数,参数会被放进 URL 查询字符串里,也可以放到请求体里。第三步,服务器收到请求以后,根据自己的业务逻辑去查数据库、调接口或者做计算,然后返回一段响应内容。第四步,浏览器收到这段响应内容,把它交给 JavaScript 代码处理。第五步,JavaScript 将处理后的数据通过 DOM API 插入到页面上。
在整个闭环里,数据的格式非常关键。最常见的返回数据是 JSON 字符串,形式长得像 JavaScript 对象,但它本质上是一段纯文本,需要用 JSON.parse 转成真正的对象才能用。早期 XML 也曾是 AJAX 返回数据的流行格式,所以 AJAX 这个名称里才会带上 XML 这个单词。直到今天,仍然有一些老系统的接口返回的是 XML 片段,这种场景下前端就需要用 DOMParser 去解析 XML,把里面的节点内容提取出来再使用。同时返回数据也有可能是纯文本、HTML 片段甚至二进制文件流,不同 Content-Type 决定了解析方式的不同。
可以把整条链路想象成一个外卖流程。你(JavaScript)在手机 App 上点了餐,这个动作类似于创建 XMLHttpRequest 对象并发起请求;餐厅(服务器)收到订单后开始做菜,这就是服务端处理业务;骑手把餐送到你手上,类似于响应内容通过网络回到浏览器。外卖盒里的饭菜是 JSON、XML 还是 HTML,直接决定你要用筷子还是勺子把它夹出来,前端代码到底该用哪种方式解析,也得看服务器返回的 Content-Type 是什么。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. AJAX工具选型解析:自己写还是用封装好的轮子
2.1 原生XMLHttpRequest:最底层的“老熟人”
讲 AJAX 实例,绕不开最底层的 XMLHttpRequest 对象。固然现在有了 fetch,写法上更简洁了,但大量历史项目、各种框架的底层封装,都直接建立在 XMLHttpRequest 之上。老规矩先看一段最原始的 GET 请求长什么样。
javascript复制var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/users?page=1', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
console.log(data);
}
};
xhr.send();
这段代码里有几个关键点必须说清楚。xhr.open 的第三个参数是 async,表示是否异步执行。当这个参数为 true 时,代码不会卡在 send 这一行等服务器返回数据,而是通过事件回调来处理结果。这里还要理解 readyState 这个状态值,它一共有 5 个状态:0 表示请求未初始化,open 还没调用;1 表示服务器连接已建立,open 已调用;2 表示请求已接收,send 已调用且头信息可用;3 表示请求处理中,响应体正在下载;4 表示请求已完成,响应内容已经可以使用了。
很多初学者容易犯的错误,是在 xhr.send() 之后立刻去读 respons
