引言
很多刚开始接触前后端交互的同学都有过一个疑问:为什么表单一提交,整个页面就"刷新"跳转了,体验很割裂?而像搜索框输一个字就弹候选词、点赞按钮点一下数字就 +1、注册时输完用户名立刻提示"已被占用"------这些页面不刷新却能实时跟服务器交互的效果,靠的正是 Ajax。
Ajax 是前后端分离开发里最基础、最核心的一项技能。理解了它,你才能明白"前端发请求 → 后端返回数据 → 前端局部更新页面"这套流程到底是怎么跑通的。这篇文章我会从最原始的原生写法讲起,一路讲到如今工程里最常用的 fetch 和 axios,并配上一个完整的 SpringBoot 前后端联调示例。
一、什么是 Ajax
Ajax 是 Asynchronous JavaScript And XML 的缩写,直译"异步 JavaScript 和 XML"。核心思想就一句话:
在不重新加载整个页面 的前提下,通过 JavaScript 向服务器发送请求、接收数据,并局部更新页面的某一块内容。
这里的"异步"是关键------发请求的时候,页面不会卡住,用户可以继续操作,等服务器返回结果后再处理。
一个对比帮你理解:
- 传统方式:整页刷新。提交表单 → 服务器处理 → 返回新的完整 HTML 页面 → 浏览器整个重载。
- Ajax 方式:局部更新。JS 发请求 → 服务器只返回一小段数据(通常是 JSON)→ JS 更新页面里的某个 DOM 节点。
如今虽然 XML 已基本被 JSON 取代,但"Ajax"这个名字沿用了下来,泛指这种"异步局部刷新"的技术方案。
二、原生 XMLHttpRequest
XMLHttpRequest(简称 XHR)是 Ajax 的"鼻祖",所有浏览器的 Ajax 能力最初都由它提供。了解它有助于理解底层的请求过程。
2.1 最简示例
javascript
javascript
// 1. 创建 XHR 对象
const xhr = new XMLHttpRequest();
// 2. 监听状态变化
xhr.onreadystatechange = function () {
// readyState === 4 表示请求完成,status === 200 表示成功
if (xhr.readyState === 4 && xhr.status === 200) {
// 3. 拿到响应文本(假设服务器返回 JSON)
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
// 4. 打开连接:GET 请求,目标地址
xhr.open('GET', '/api/user/1', true); // 第三个参数 true 表示异步
// 5. 发送请求
xhr.send();
readyState 的几个值:
| readyState | 含义 |
|---|---|
| 0 | 未初始化(open 还没调用) |
| 1 | 已调用 open |
| 2 | 已调用 send,收到响应头 |
| 3 | 正在接收响应体 |
| 4 | 请求完成 |
2.2 发送 POST 请求
javascript
javascript
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('成功', xhr.responseText);
} else {
console.log('失败', xhr.status);
}
}
};
xhr.open('POST', '/api/user/save', true);
// 设置请求头,告诉服务器发送的是 JSON
xhr.setRequestHeader('Content-Type', 'application/json');
// 发送 JSON 字符串
xhr.send(JSON.stringify({ name: '张三', age: 18 }));
2.3 封装一个简单的 ajax 函数
原生写法重复代码太多,我们简单封装一下:
javascript
javascript
function ajax({ method = 'GET', url, data = null }) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error('请求失败:' + xhr.status));
}
};
xhr.send(data ? JSON.stringify(data) : null);
});
}
// 使用
ajax({ url: '/api/user/1' })
.then(data => console.log(data))
.catch(err => console.error(err));
三、现代推荐:fetch API
XMLHttpRequest 的回调写法比较繁琐,于是浏览器原生提供了更简洁、基于 Promise 的 fetch。
3.1 基本 GET 请求
javascript
javascript
fetch('/api/user/1')
.then(response => response.json()) // 把响应体解析成 JSON
.then(data => console.log(data))
.catch(err => console.error('请求出错', err));
3.2 发送 POST 请求
javascript
javascript
fetch('/api/user/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: '张三', age: 18 })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
3.3 配合 async / await
javascript
javascript
async function getUser(id) {
const response = await fetch('/api/user/' + id);
const data = await response.json();
return data;
}
3.4 一个必须记住的坑
fetch 只有在"网络错误"(如断网、域名解析失败)时才会 reject;对于 404、500 这类 HTTP 错误状态码,fetch 并不会 reject ,仍然会正常 resolve,只是 response.ok 为 false。所以要这样判断:
javascript
javascript
fetch('/api/user/999')
.then(response => {
if (!response.ok) {
// HTTP 状态码 4xx / 5xx,手动抛错
throw new Error('HTTP 错误:' + response.status);
}
return response.json();
})
.then(data => console.log(data))
.catch(err => console.error(err));
这一点和 axios 不一样,很多新手在这栽过跟头。
四、工程主流:axios
实际项目里,用得最多的还是 axios,它对 fetch 和 XHR 做了更友好的封装,功能也更全(拦截器、超时、取消请求、进度、自动 JSON 转换等)。
html
html
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
javascript
javascript
// GET
axios.get('/api/user/1')
.then(res => console.log(res.data))
.catch(err => console.error(err));
// POST
axios.post('/api/user/save', { name: '张三', age: 18 })
.then(res => console.log(res.data))
.catch(err => console.error(err));
// 统一配置 + 拦截器(工程常用)
const http = axios.create({ baseURL: '/api', timeout: 5000 });
http.interceptors.response.use(
res => res.data, // 直接返回 data
err => { // 统一错误处理
console.error('请求失败', err);
return Promise.reject(err);
}
);
axios 的 catch 对 HTTP 错误码(4xx/5xx)也会进入 reject,比 fetch 省心。
五、实战:配合 SpringBoot 前后端联调
下面给一个完整可运行的例子:前端用 Ajax 请求后端接口,实现"查询用户 + 新增用户"。后端沿用你熟悉的 SpringBoot。
5.1 后端接口
java
java
@RestController
@RequestMapping("/api/user")
public class UserController {
// 用一个 Map 简单模拟数据库
private final Map<Long, String> users = new ConcurrentHashMap<>();
@GetMapping("/{id}")
public Map<String, Object> getUser(@PathVariable Long id) {
String name = users.get(id);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("data", name == null ? "未找到" : name);
return result;
}
@PostMapping("/save")
public Map<String, Object> saveUser(@RequestBody Map<String, Object> body) {
Long id = Long.valueOf(body.get("id").toString());
String name = body.get("name").toString();
users.put(id, name);
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "保存成功");
return result;
}
}
5.2 前端页
html
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Ajax 示例</title>
</head>
<body>
<h2>查询用户</h2>
<input id="uid" type="number" placeholder="输入用户 ID">
<button onclick="query()">查询</button>
<p id="result"></p>
<h2>新增用户</h2>
<input id="newId" type="number" placeholder="ID">
<input id="newName" type="text" placeholder="姓名">
<button onclick="save()">保存</button>
<script>
// 查询(fetch)
async function query() {
const id = document.getElementById('uid').value;
const resp = await fetch('/api/user/' + id);
const data = await resp.json();
document.getElementById('result').innerText = JSON.stringify(data);
}
// 新增(fetch POST)
async function save() {
const id = document.getElementById('newId').value;
const name = document.getElementById('newName').value;
const resp = await fetch('/api/user/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: Number(id), name: name })
});
const data = await resp.json();
alert(data.msg);
}
</script>
</body>
</html>
把这个 index.html 放到 SpringBoot 的 src/main/resources/static/ 目录下,启动项目访问 http://localhost:8080 即可体验:点"查询"和"保存",页面不会刷新,结果却实时显示------这就是 Ajax。
六、常见问题与坑
-
跨域(CORS) :前端页面和后端接口不在同一个域名/端口时,浏览器会拦截响应。解决方式有后端加
@CrossOrigin注解、配置 CORS、或用 Nginx 反向代理。同源部署(上面示例把 HTML 放进 static 目录)则天然无跨域问题。 -
fetch 不 reject HTTP 错误 :上文强调过,记得用
response.ok判断,或用 axios。 -
GET/POST 数据格式不对 :GET 参数拼在 URL 上;POST 传 JSON 时要设置
Content-Type: application/json并用JSON.stringify序列化。 -
回调地狱:异步结果要在回调里处理,嵌套多了可读性差。用 Promise 链式调用或 async/await 解决。
-
忘记 await / 顺序错乱 :异步函数里如果漏了
await,代码会继续往下执行,拿到的是 Promise 而不是数据。
总结
Ajax 是前后端分离的基石,掌握它就等于拿到了"前端如何跟后端对话"的钥匙。本文梳理了三条技术路线:
- XMLHttpRequest:最底层、最原始,理解原理必学,但写起来繁琐。
- fetch:浏览器原生、基于 Promise,现代开发推荐,但要注意"HTTP 错误不 reject"的坑。
- axios:功能最全、工程最主流,自带拦截器、超时、自动 JSON 处理,团队项目首选。