Ajax 请求方法 GET POST PUT DELETE 详解与实战 前端开发者必看
说实话,很多刚入行的小伙伴对 Ajax 请求的理解还停留在”能用就行”的层面。今天咱们就把它掰开了、揉碎了讲清楚,让你不仅会用,还能明白为什么这么用。
一、为什么要有这四种方法?
先把背景说清楚。HTTP 协议定义了多种请求方法,其中最常用的就是 GET、POST、PUT、DELETE,它们合称 RESTful 风格的操作。你可以把它们想象成对数据库里某条数据的四种基本操作:
- GET —— 查
- POST —— 增
- PUT —— 改(整体替换)
- DELETE —— 删
听起来很简单对吧?但真正用的时候,90% 的人都在用错。接下来我们一个个拆解。
二、GET 请求:最熟悉也最容易踩坑
基本概念
GET 是用来获取数据的,它会把参数拼在 URL 后面。比如:
https://api.example.com/users?page=1&size=10
参数就是 page=1&size=10,直接暴露在 URL 里,所以敏感信息千万别用 GET 传。
代码实战
我们用浏览器自带的 fetch API 来演示(现在几乎没人用原始的 XMLHttpRequest 了,除非你要兼容 IE):
// 最简单的 GET 请求
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('请求失败:', error));
带参数的 GET 请求,有两种写法,我推荐第二种:
// 写法一:手动拼接 URL(容易出错,不推荐)
const url = 'https://api.example.com/users?page=' + page + '&size=' + size;
fetch(url);
// 写法二:用 URLSearchParams(清晰、安全)
const params = new URLSearchParams({
page: 1,
size: 10,
keyword: '张三'
});
fetch(`https://api.example.com/users?${params}`);
实际项目中的常见用法
假设你在做一个搜索列表页,用户输入关键词后触发请求:
async function searchUsers(keyword) {
// 防抖处理,避免用户每敲一个字就发一次请求
const params = new URLSearchParams({ keyword });
try {
const response = await fetch(`https://api.example.com/users?${params}`);
if (!response.ok) {
throw new Error(`HTTP 错误!状态码:${response.status}`);
}
const result = await response.json();
return result.data; // 假设接口返回 { code: 200, data: [...], total: 100 }
} catch (error) {
console.error('搜索失败', error);
return [];
}
}
// 在搜索框的 input 事件中使用
searchInput.addEventListener('input', debounce(async (e) => {
const results = await searchUsers(e.target.value);
renderList(results);
}, 300));
GET 请求的三个重要特性
- 幂等 —— 多次执行 GET 请求,结果完全一样,不会产生副作用。这是 HTTP 规范的要求,后端也必须遵守。
- 有缓存 —— 浏览器会对 GET 请求做缓存,这就是为什么刷新页面时 GET 请求有时候不会真的发网络请求。
- 长度限制 —— URL 长度有限制(不同浏览器不一样,一般 2KB~8KB),所以别试图用 GET 传大段数据。
三、POST 请求:提交数据的主战场
基本概念
POST 用来提交数据,参数放在请求体(Request Body)里,不会暴露在 URL 中。适合传敏感信息、大段数据、文件上传等场景。
代码实战
// 提交表单数据
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 如果有鉴权,加上 token
// 'Authorization': 'Bearer ' + token
},
body: JSON.stringify(userData)
});
if (!response.ok) {
// 统一处理错误
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `请求失败:${response.status}`);
}
const result = await response.json();
return result.data; // 返回创建的用户信息
} catch (error) {
console.error('创建用户失败', error);
throw error; // 把错误抛出去,让调用方决定怎么处理
}
}
// 使用示例
createUser({
name: '小明',
email: 'xiaoming@example.com',
age: 25
}).then(user => {
console.log('创建成功', user);
});
POST 的两种常见数据格式
很多新手分不清 application/json 和 application/x-www-form-urlencoded 的区别,这里详细说:
1. application/json(现代项目主流)
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: '小明', age: 25 })
});
请求体长这样:
{"name":"小明","age":25}
2. application/x-www-form-urlencoded(传统表单提交)
const formData = new URLSearchParams();
formData.append('name', '小明');
formData.append('age', '25');
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formData.toString()
});
请求体长这样:
name=%E5%B0%8F%E6%98%8E&age=25
💡 选哪个? 前端项目几乎全选 JSON 格式。除非你要兼容一些老的后端接口,或者做表单提交且不想引入额外逻辑,才用 form 格式。
文件上传的 POST
async function uploadAvatar(file) {
const formData = new FormData();
formData.append('avatar', file); // 第一个参数是后端字段名
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
// 注意:用 FormData 时不要手动设置 Content-Type
// 浏览器会自动设置,并加上 boundary 参数
});
return await response.json();
}
四、PUT 请求:整体更新
基本概念
PUT 用来更新资源,但它的核心语义是”整体替换”。也就是说,你 PUT 过去什么,服务端就把那条记录完整覆盖成什么样。如果某字段你没传,服务端可能会把它置空。
这点非常重要,也是 PUT 和 PATCH 的区别所在。
代码实战
// 用 PUT 更新一个用户的完整信息
async function updateUser(userId, fullUserData) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(fullUserData)
// fullUserData 应该包含该用户的所有字段
// 比如 { name: '小明', email: 'xxx@.com', age: 26, role: 'admin' }
});
if (!response.ok) {
throw new Error(`更新失败:${response.status}`);
}
return await response.json();
} catch (error) {
console.error('更新用户失败', error);
throw error;
}
}
// 调用示例
updateUser('123', {
name: '小明',
email: 'xiaoming_new@example.com',
age: 26,
role: 'user'
});
PUT vs PATCH 怎么选?
这是面试常考题,也是实际开发中最容易混淆的地方:
| 特性 | PUT | PATCH |
|---|---|---|
| 语义 | 整体替换 | 部分更新 |
| 必需字段 | 需要传完整数据 | 只传要改的字段 |
| 幂等性 | ✅ 幂等 | ❌ 不保证幂等 |
| 适用场景 | 表单完整编辑页 | 只改某一个字段 |
举个例子:
// PUT:用户编辑整个表单,提交完整数据
PUT /api/users/123
Body: { "name": "小明", "email": "a@b.com", "age": 26, "role": "user" }
// PATCH:用户只改了昵称
PATCH /api/users/123
Body: { "name": "小明同学" }
📌 实际项目中,很多后端不太严格区分这两个,所以你们对接的时候最好先确认一下接口文档。别想当然。
五、DELETE 请求:删数据
基本概念
DELETE 用来删除资源,通常只需要传要删除的 ID,参数放在 URL 里。比如 DELETE /api/users/123。
代码实战
// 删除单个用户
async function deleteUser(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
// DELETE 请求一般不需要 body,如果后端需要可以传
});
if (!response.ok) {
throw new Error(`删除失败:${response.status}`);
}
// 有些后端删除成功返回 204 No Content,没有 body
// 这时候 response.json() 会报错,需要特殊处理
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('json')) {
return await response.json();
}
return { success: true };
} catch (error) {
console.error('删除用户失败', error);
throw error;
}
}
// 删除多个用户
async function deleteUsers(userIds) {
const response = await fetch('https://api.example.com/users/batch', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids: userIds })
});
return await response.json();
}
DELETE 请求的安全提示
删除操作是不可逆的,前端一定要加确认:
async function handleDelete(userId, userName) {
const confirmed = confirm(`确定要删除用户「${userName}」吗?此操作不可恢复。`);
if (!confirmed) return;
try {
await deleteUser(userId);
showToast('删除成功', 'success');
refreshList(); // 删除后刷新列表
} catch (error) {
showToast('删除失败,请重试', 'error');
}
}
六、封装一个通用的请求工具
实际项目中,你不会在每个地方都写一遍 fetch。通常会封装一个统一的工具函数,处理拦截、错误、超时等公共逻辑。
// request.js
const BASE_URL = 'https://api.example.com';
const TIMEOUT = 10000;
class Request {
constructor() {
this.pendingRequests = new Set();
}
// 生成唯一请求 ID,用于取消重复请求
generateRequestId(method, url) {
return `${method}_${url}`;
}
// 核心请求方法
async request(options) {
const {
url,
method = 'GET',
data, // 用于 GET/DELETE 的 query 参数
body, // 用于 POST/PUT/PATCH 的请求体
headers = {},
timeout = TIMEOUT,
cancelToken // 用于取消请求
} = options;
// 拼接 URL 和参数
let fullUrl = `${BASE_URL}${url}`;
if (data && (method === 'GET' || method === 'DELETE')) {
const params = new URLSearchParams(data);
fullUrl += `?${params}`;
}
// 构建请求配置
const config = {
method,
headers: {
'Content-Type': 'application/json',
...headers
}
};
// POST/PUT/PATCH 才需要 body
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
config.body = JSON.stringify(body);
}
// 超时控制
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
config.signal = controller.signal;
try {
const response = await fetch(fullUrl, config);
clearTimeout(timer);
// 统一响应处理
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw {
status: response.status,
message: errorData.message || `请求失败:${response.status}`,
data: errorData
};
}
// 处理 204 No Content
if (response.status === 204) {
return { code: 204, data: null };
}
return await response.json();
} catch (error) {
clearTimeout(timer);
// 区分超时和网络错误
if (error.name === 'AbortError') {
throw { status: 0, message: '请求超时,请稍后重试' };
}
throw error;
}
}
// 便捷方法
get(url, data, options = {}) {
return this.request({ url, method: 'GET', data, ...options });
}
post(url, body, options = {}) {
return this.request({ url, method: 'POST', body, ...options });
}
put(url, body, options = {}) {
return this.request({ url, method: 'PUT', body, ...options });
}
delete(url, data, options = {}) {
return this.request({ url, method: 'DELETE', data, ...options });
}
}
// 导出单例
export default new Request();
使用方式:
import request from './request';
// GET
const users = await request.get('/users', { page: 1, size: 10 });
// POST
const newUser = await request.post('/users', { name: '小明', age: 25 });
// PUT
const updatedUser = await request.put('/users/123', { name: '小明', age: 26 });
// DELETE
await request.delete('/users/123');
七、常见坑点总结
这里把实际开发中经常遇到的坑整理一下:
1. 跨域问题(CORS)
Access to fetch at 'https://api.example.com' from origin 'https://your-site.com'
has been blocked by CORS policy
这是前端最常遇到的报错。解决方案在后端配置响应头:
Access-Control-Allow-Origin: https://your-site.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
前端这边可以用代理解决开发环境问题,Vite 配置:
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
};
2. POST 请求变成了 OPTIONS(预检请求)
当请求的 Content-Type 不是简单类型(比如用了 JSON),浏览器会先发一个 OPTIONS 请求做预检。如果后端没处理,你就会看到 403 错误。确保后端正确处理了 OPTIONS 请求。
3. PUT 和 PATCH 搞混
再次强调:PUT 是整体替换,PATCH 是部分更新。别用 PUT 只传一个字段,除非你确定后端支持。
4. DELETE 请求带 body
有些后端框架(比如 Spring Boot)默认不支持 DELETE 请求的 body。如果需要传,可以考虑用 POST 或者换用查询参数。
5. 重复提交
表单提交、列表删除等场景,用户可能连续点击多次。前端要做防抖/节流,或者提交后禁用按钮:
async function handleSubmit() {
if (isSubmitting) return; // 防止重复提交
setIsSubmitting(true);
try {
await request.post('/api/users', formData);
showToast('提交成功');
} finally {
setIsSubmitting(false);
}
}
八、一句话记住它们
- GET → 查数据,参数放 URL,可缓存,不传敏感信息
- POST → 创建数据,参数放 Body,最灵活,前端主力
- PUT → 整体替换更新,参数放 Body,幂等
- DELETE → 删除数据,ID 放 URL,务必加确认
把这四种方法记牢了,Ajax 请求这块你就基本过关了。后续进阶可以去看看 GraphQL、WebSocket 这些,但基础永远是最重要的。有任何问题随时来问,祝编码愉快!
