说到前端和网络请求,很多刚入行的小伙伴都会觉得这一坨概念有点乱。今天咱们就把 XMLHttpRequest、fetch 和 axios 这三位“选手”掰开了揉碎了讲清楚,顺便把 RESTful 里那几个常见的 HTTP 方法(GET、POST、PUT、DELETE)也理得明明白白。你不用背定义,咱们用大白话加上真实代码场景来聊。
先聊聊历史:那个古老又熟悉的 XMLHttpRequest
要理解现在的技术,得回头看看。在现代前端开发诞生之前,XMLHttpRequest(简称 XHR)是唯一的王。它是浏览器原生提供的 API,用来发送 HTTP 请求。
虽然它现在已经显得有点过时,但很多老项目里还在用它,而且理解它有助于你明白为什么后来的 fetch 要设计成那样。
XHR 的核心特点是基于事件驱动,使用回调函数来处理请求的不同阶段(比如请求发送中、响应接收中、完成等)。它的写法非常啰嗦,层级嵌套深,也就是我们常说的“回调地狱”。
看个典型的 XHR 请求例子:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/users', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { // 请求已完成
if (xhr.status === 200) { // 请求成功
var users = JSON.parse(xhr.responseText);
console.log('获取用户列表成功:', users);
} else {
console.error('请求失败,状态码:', xhr.status);
}
}
};
xhr.onerror = function () {
console.error('网络错误');
};
xhr.ontimeout = function () {
console.error('请求超时');
};
xhr.timeout = 3000; // 设置超时时间为3秒
xhr.send();
你看,是不是特别繁琐?每次都要创建对象、绑定多种事件、手动解析 JSON。对于刚学前端的小朋友来说,这简直就是噩梦。不过,它有一个好处:兼容性极好,几乎支持所有浏览器,包括一些非常古老的 IE 版本。
现代的选择:fetch API
随着 HTML5 和 ES6 的普及,浏览器原生提供了一个更简洁、更强大的请求工具——fetch。它是基于 Promise 的,这意味着我们可以用更现代的语法来写异步代码。
fetch 最大的改变是:用法更简单,逻辑更清晰。
看同样的请求,用 fetch 怎么写:
fetch('https://api.example.com/users')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('获取用户列表成功:', data);
})
.catch(error => {
console.error('请求失败:', error);
});
是不是简洁多了?不用手动解析 JSON,不用处理复杂的 readyState 状态。fetch 的 Promise 特性让它很容易和 async/await 配合使用:
async function getUsers() {
try {
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
console.log('获取用户列表成功:', users);
} catch (error) {
console.error('请求失败:', error);
}
}
getUsers();
虽然 fetch 很棒,但它也不是完美的。它有几个小坑:
- 默认不携带 cookie:除非你明确设置
credentials: 'include'。 - 不会在 HTTP 错误时拒绝 Promise:比如 404、500 状态码,fetch 不会自动抛出错误,你需要手动检查
response.ok。 - 取消请求比较麻烦:需要借助
AbortController,而且不是所有浏览器都支持。
最受欢迎的第三方库:axios
如果你觉得原生 fetch 还不够方便,或者你的项目需要更强大的功能,那 axios 就是不错的选择。axios 不是一个浏览器 API,而是一个基于 Promise 的 HTTP 客户端库,可以在浏览器和 Node.js 环境中使用。
axios 的优点非常多:
- 自动转换 JSON 数据:不需要手动调用
.json()。 - 请求和响应拦截器:可以统一处理 token、错误日志等。
- 取消请求:支持使用
CancelToken或AbortController轻松取消请求。 - 自动转换请求和响应数据:比如自动将请求数据序列化为 JSON。
- 客户端支持防止 CSRF:内置了一些安全机制。
看一个 axios 的简单例子:
// 引入 axios(如果使用 npm)
import axios from 'axios';
// 或者在 HTML 中直接引入
// <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// 发送 GET 请求
axios.get('https://api.example.com/users')
.then(response => {
console.log('获取用户列表成功:', response.data);
})
.catch(error => {
console.error('请求失败:', error);
});
// 使用 async/await
async function getUsers() {
try {
const response = await axios.get('https://api.example.com/users');
console.log('获取用户列表成功:', response.data);
} catch (error) {
console.error('请求失败:', error);
}
}
axios 还允许你创建一个拦截器,这在大型项目中特别有用。比如,你可以在所有请求发出前自动添加 token,在所有响应返回后统一处理错误:
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么,比如添加 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
// 对响应数据做点什么,比如统一处理业务错误
if (response.data.code !== 200) {
return Promise.reject(new Error(response.data.message));
}
return response;
},
function (error) {
// 对响应错误做点什么,比如统一处理 HTTP 错误
if (error.response.status === 401) {
// 未授权,跳转到登录页
window.location.href = '/login';
}
return Promise.reject(error);
}
);
三者怎么选?
这是一个很实际的问题。很多开发者会问:“我该用哪个?”
其实没有绝对的答案,取决于你的项目需求和团队习惯。我给你一个简单粗暴的选择指南:
- 如果你追求零依赖、现代浏览器项目:用
fetch。它的原生支持让它成为轻量级项目的首选,不需要引入额外的库。 - 如果你需要兼容旧浏览器(比如 IE):用
XMLHttpRequest,或者引入fetchpolyfill。 - 如果你需要强大的功能(拦截器、取消请求、自动转换数据等):用
axios。它是目前最流行的 HTTP 客户端库,社区支持好,文档齐全。
在很多实际项目中,你会发现 axios 是更普遍的选择,因为它封装了太多实用的功能,让开发效率大大提高。而 fetch 则适合那些希望保持代码简洁、减少依赖的场景。
HTTP 方法:GET、POST、PUT、DELETE 的区别
讲完了请求工具,咱们再聊聊 HTTP 方法。在 RESTful API 设计中,这四个方法是最常用的。它们分别对应着 CRUD(创建、读取、更新、删除)操作。
GET:获取数据
GET 方法用于从服务器获取数据。它是幂等的(多次请求结果相同)且安全的(不会改变服务器状态)。
使用场景:
- 获取用户列表
- 查询商品详情
- 搜索内容
GET 请求的参数通常放在 URL 中,比如:
GET /api/users?page=1&size=10
代码示例:
// fetch 示例
fetch('/api/users?page=1&size=10')
.then(response => response.json())
.then(data => console.log(data));
// axios 示例
axios.get('/api/users', {
params: {
page: 1,
size: 10
}
}).then(response => console.log(response.data));
POST:创建数据
POST 方法用于向服务器提交数据,通常用于创建新资源。它不是幂等的(多次提交可能创建多个资源),也不安全(会改变服务器状态)。
使用场景:
- 用户注册
- 提交订单
- 上传图片
POST 请求的参数通常放在请求体(request body)中。
代码示例:
// fetch 示例
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'zhangsan',
email: 'zhangsan@example.com',
password: '123456'
})
})
.then(response => response.json())
.then(data => console.log('用户创建成功:', data));
// axios 示例
axios.post('/api/users', {
username: 'zhangsan',
email: 'zhangsan@example.com',
password: '123456'
}).then(response => console.log('用户创建成功:', response.data));
PUT:更新数据
PUT 方法用于更新服务器上的资源。它是幂等的(多次请求结果相同),通常用于替换整个资源。
使用场景:
- 更新用户信息
- 修改商品详情
PUT 请求的参数也放在请求体中。注意,PUT 通常是全量更新,即你需要提供资源的所有字段。
代码示例:
// fetch 示例
fetch('/api/users/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: 123,
username: 'zhangsan',
email: 'zhangsan_new@example.com',
password: 'newpassword'
})
})
.then(response => response.json())
.then(data => console.log('用户更新成功:', data));
// axios 示例
axios.put('/api/users/123', {
id: 123,
username: 'zhangsan',
email: 'zhangsan_new@example.com',
password: 'newpassword'
}).then(response => console.log('用户更新成功:', response.data));
DELETE:删除数据
DELETE 方法用于从服务器删除资源。它是幂等的(多次删除结果相同),通常用于删除指定 ID 的资源。
使用场景:
- 删除用户
- 删除订单
- 删除商品
DELETE 请求通常不需要请求体,删除的资源 ID 放在 URL 中。
代码示例:
// fetch 示例
fetch('/api/users/123', {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
console.log('用户删除成功');
}
});
// axios 示例
axios.delete('/api/users/123')
.then(response => console.log('用户删除成功'));
实际项目中的完整案例
光说不练假把式。咱们来模拟一个完整的前后端交互场景:一个用户管理页面。这个页面需要实现:
- 显示用户列表
- 添加新用户
- 修改用户信息
- 删除用户
我们用 axios 来实现,因为它功能更全面。
1. 显示用户列表(GET)
// 获取用户列表
async function fetchUsers() {
try {
const response = await axios.get('/api/users', {
params: {
page: 1,
size: 10
}
});
return response.data;
} catch (error) {
console.error('获取用户列表失败:', error);
throw error;
}
}
// 渲染用户列表
async function renderUserList() {
const users = await fetchUsers();
const userListElement = document.getElementById('user-list');
userListElement.innerHTML = users.map(user => `
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.email}</td>
<td>
<button onclick="editUser(${user.id})">编辑</button>
<button onclick="deleteUser(${user.id})">删除</button>
</td>
</tr>
`).join('');
}
2. 添加新用户(POST)
// 添加用户
async function addUser(userData) {
try {
const response = await axios.post('/api/users', userData);
return response.data;
} catch (error) {
console.error('添加用户失败:', error);
throw error;
}
}
// 处理表单提交
document.getElementById('add-user-form').addEventListener('submit', async function(e) {
e.preventDefault();
const userData = {
username: document.getElementById('username').value,
email: document.getElementById('email').value,
password: document.getElementById('password').value
};
try {
await addUser(userData);
alert('用户添加成功!');
renderUserList(); // 重新加载用户列表
this.reset(); // 清空表单
} catch (error) {
alert('添加用户失败,请重试。');
}
});
3. 修改用户信息(PUT)
// 编辑用户
async function editUser(userId) {
try {
const response = await axios.get(`/api/users/${userId}`);
const user = response.data;
// 填充表单
document.getElementById('edit-user-id').value = user.id;
document.getElementById('edit-username').value = user.username;
document.getElementById('edit-email').value = user.email;
// 显示编辑模态框
document.getElementById('edit-user-modal').style.display = 'block';
} catch (error) {
console.error('获取用户信息失败:', error);
alert('获取用户信息失败,请重试。');
}
}
// 保存修改
document.getElementById('save-user-btn').addEventListener('click', async function() {
const userId = document.getElementById('edit-user-id').value;
const userData = {
id: userId,
username: document.getElementById('edit-username').value,
email: document.getElementById('edit-email').value
};
try {
await axios.put(`/api/users/${userId}`, userData);
alert('用户信息更新成功!');
document.getElementById('edit-user-modal').style.display = 'none';
renderUserList(); // 重新加载用户列表
} catch (error) {
console.error('更新用户失败:', error);
alert('更新用户失败,请重试。');
}
});
4. 删除用户(DELETE)
// 删除用户
async function deleteUser(userId) {
if (!confirm('确定要删除这个用户吗?此操作不可恢复。')) {
return;
}
try {
await axios.delete(`/api/users/${userId}`);
alert('用户删除成功!');
renderUserList(); // 重新加载用户列表
} catch (error) {
console.error('删除用户失败:', error);
alert('删除用户失败,请重试。');
}
}
总结一下
好了,咱们今天聊了不少内容。回顾一下:
- XMLHttpRequest:古老但兼容性好,写法啰嗦,适合老项目或需要兼容旧浏览器的场景。
- fetch:现代原生 API,基于 Promise,简洁易用,但功能相对有限。
- axios:第三方库,功能强大,支持拦截器、取消请求等,是目前最流行的选择。
对于 HTTP 方法:
- GET:获取数据,参数放 URL。
- POST:创建数据,参数放请求体。
- PUT:更新数据,参数放请求体,通常全量更新。
- DELETE:删除数据,资源 ID 放 URL。
在实际项目中,我建议优先考虑 axios,因为它提供的功能能极大提升开发效率。如果你的项目对体积有严格要求,或者不想引入额外依赖,可以用 fetch。至于 XMLHttpRequest,除非你有特殊的兼容性需求,否则没必要再去用了。
希望这篇文章能帮你理清思路,下次再遇到这些问题,你就知道该怎么选了!如果有其他疑问,欢迎随时交流。
