Axios 和 Fetch 对比及统一请求封装
1. Axios 基础用法
1.1 安装和引入
javascript
// 安装
npm install axios
// 引入
import axios from 'axios';
1.2 基本请求方法
javascript
// GET 请求
axios.get('/api/users')
.then(response => console.log(response.data))
.catch(error => console.error(error));
// POST 请求
axios.post('/api/users', {
name: 'John',
age: 30
})
.then(response => console.log(response.data))
.catch(error => console.error(error));
// PUT 请求
axios.put('/api/users/1', {
name: 'John',
age: 31
});
// DELETE 请求
axios.delete('/api/users/1');
// 使用 async/await
async function fetchData() {
try {
const response = await axios.get('/api/users');
console.log(response.data);
} catch (error) {
console.error(error);
}
}
1.3 配置选项
javascript
// 全局配置
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = 'Bearer token';
axios.defaults.timeout = 5000;
// 请求配置
axios({
method: 'post',
url: '/api/users',
data: {
name: 'John',
age: 30
},
headers: {
'Content-Type': 'application/json'
},
timeout: 5000,
withCredentials: true
});
// 创建实例
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {'X-Custom-Header': 'value'}
});
2. Fetch 基础用法
2.1 基本请求方法
javascript
// GET 请求
fetch('/api/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// POST 请求
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'John',
age: 30
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// 使用 async/await
async function fetchData() {
try {
const response = await fetch('/api/users');
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
2.2 请求配置
javascript
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify(data),
mode: 'cors',
credentials: 'include',
cache: 'no-cache',
redirect: 'follow'
});
3. Axios 和 Fetch 的主要区别
3.1 功能差异
- 请求/响应拦截
javascript
// Axios 支持拦截器
axios.interceptors.request.use(config => {
// 请求前处理
return config;
}, error => {
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
// 响应后处理
return response;
}, error => {
return Promise.reject(error);
});
// Fetch 需要自行封装
const fetchWithInterceptor = async (...args) => {
// 请求前处理
const response = await fetch(...args);
// 响应后处理
return response;
};
- 自动转换 JSON
javascript
// Axios 自动转换 JSON
axios.get('/api/users')
.then(response => console.log(response.data)); // 直接获取数据
// Fetch 需要手动转换
fetch('/api/users')
.then(response => response.json())
.then(data => console.log(data));
- 错误处理
javascript
// Axios 统一处理错误
axios.get('/api/users')
.catch(error => {
if (error.response) {
// 服务器响应错误
console.log(error.response.status);
} else if (error.request) {
// 请求未收到响应
console.log(error.request);
} else {
// 请求配置错误
console.log(error.message);
}
});
// Fetch 需要手动检查状态
fetch('/api/users')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch(error => console.log(error));
3.2 浏览器支持
- Fetch: 现代浏览器原生支持,但不支持 IE
- Axios: 支持所有主流浏览器,包括 IE11
4. React 统一请求封装
4.1 基于 Axios 的封装
typescript
// src/utils/request.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { message } from 'antd';
// 请求配置类型
interface RequestConfig extends AxiosRequestConfig {
loading?: boolean;
skipErrorHandler?: boolean;
}
// 响应数据类型
interface ResponseData<T = any> {
code: number;
data: T;
message: string;
}
class Request {
private instance: AxiosInstance;
private loading: boolean;
constructor() {
this.instance = axios.create({
baseURL: process.env.REACT_APP_API_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
this.loading = false;
this.setupInterceptors();
}
private setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config: RequestConfig) => {
// 添加 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 显示加载状态
if (config.loading) {
this.loading = true;
// 可以使用 antd 的 loading 组件
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse<ResponseData>) => {
// 隐藏加载状态
this.loading = false;
const { code, data, message: msg } = response.data;
// 处理业务错误
if (code !== 0) {
if (!response.config.skipErrorHandler) {
message.error(msg || '请求失败');
}
return Promise.reject(new Error(msg || '请求失败'));
}
return data;
},
error => {
// 隐藏加载状态
this.loading = false;
// 处理 HTTP 错误
if (!error.config?.skipErrorHandler) {
if (error.response) {
switch (error.response.status) {
case 401:
message.error('未授权,请重新登录');
// 可以在这里处理登出逻辑
break;
case 403:
message.error('拒绝访问');
break;
case 404:
message.error('请求的资源不存在');
break;
case 500:
message.error('服务器错误');
break;
default:
message.error(`请求失败: ${error.message}`);
}
} else {
message.error('网络错误,请检查网络连接');
}
}
return Promise.reject(error);
}
);
}
// 通用请求方法
public async request<T = any>(config: RequestConfig): Promise<T> {
try {
const response = await this.instance.request<any, T>(config);
return response;
} catch (error) {
return Promise.reject(error);
}
}
// GET 请求
public get<T = any>(url: string, config?: RequestConfig): Promise<T> {
return this.request({ ...config, method: 'GET', url });
}
// POST 请求
public post<T = any>(url: string, data?: any, config?: RequestConfig): Promise<T> {
return this.request({ ...config, method: 'POST', url, data });
}
// PUT 请求
public put<T = any>(url: string, data?: any, config?: RequestConfig): Promise<T> {
return this.request({ ...config, method: 'PUT', url, data });
}
// DELETE 请求
public delete<T = any>(url: string, config?: RequestConfig): Promise<T> {
return this.request({ ...config, method: 'DELETE', url });
}
}
export default new Request();
4.2 使用示例
typescript
// src/api/user.ts
import request from '@/utils/request';
interface User {
id: number;
name: string;
age: number;
}
export const userApi = {
// 获取用户列表
getUsers: () => request.get<User[]>('/api/users'),
// 获取用户详情
getUser: (id: number) => request.get<User>(`/api/users/${id}`),
// 创建用户
createUser: (data: Omit<User, 'id'>) =>
request.post<User>('/api/users', data),
// 更新用户
updateUser: (id: number, data: Partial<User>) =>
request.put<User>(`/api/users/${id}`, data),
// 删除用户
deleteUser: (id: number) =>
request.delete(`/api/users/${id}`)
};
// 在组件中使用
import React, { useEffect, useState } from 'react';
import { userApi } from '@/api/user';
const UserList: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
const fetchUsers = async () => {
try {
const data = await userApi.getUsers();
setUsers(data);
} catch (error) {
console.error('Failed to fetch users:', error);
}
};
fetchUsers();
}, []);
const handleCreateUser = async () => {
try {
const newUser = await userApi.createUser({
name: 'John',
age: 30
});
setUsers(prev => [...prev, newUser]);
} catch (error) {
console.error('Failed to create user:', error);
}
};
return (
<div>
{/* 渲染用户列表 */}
</div>
);
};
export default UserList;
5. 最佳实践建议
1. 选择建议
- 小型项目:使用 Fetch 足够
- 大型项目:推荐使用 Axios(功能更完善)
- 需要兼容 IE:必须使用 Axios
2. 性能优化
- 合理使用请求取消
- 实现请求缓存
- 避免重复请求
3. 安全考虑
- 统一处理敏感信息
- 实现请求超时
- CSRF 防护
4. 开发建议
- 使用 TypeScript 增加类型安全
- 集中管理 API 请求
- 做好错误处理
- 添加请求日志
6. fetch实现原理
fetch 是一种用于在浏览器中发起 HTTP 请求并获取响应的 API,它是 XMLHttpRequest 的现代替代品。fetch 基于 Promise,使得异步代码更加简洁和易于理解。它的实现原理是利用浏览器提供的低级 API 来执行 HTTP 请求和处理响应。
1. fetch 的基本使用
fetch 接口允许你通过网络发起请求并返回响应,它的基本语法如下:
javascript
fetch(url, options)
.then(response => response.json()) // 或者 response.text(), response.blob() 等
.then(data => console.log(data))
.catch(error => console.log(error));
url:要请求的资源地址。
options(可选):一个包含配置选项的对象,用于配置请求方法、请求头、请求体等。
2. fetch 的底层实现原理
fetch 是基于浏览器的 Fetch API 实现的,它的实现原理依赖于以下几个关键步骤:
2.1 发起 HTTP 请求
当你调用 fetch 时,浏览器通过 HTTP 协议 向目标 URL 发送请求。这个请求可以是 GET、POST、PUT 等类型,并且可以包含请求头和请求体(例如,POST 请求通常包含数据)。
在底层,fetch 会使用 浏览器的网络栈(例如,TCP/IP 协议、TLS/SSL 等)通过 DNS 查询目标服务器的 IP 地址,建立网络连接,并发送 HTTP 请求。
2.2 异步请求与 Promise
fetch 是基于 Promise 的,它允许我们在请求完成后异步处理结果。这意味着当你调用 fetch 时,浏览器会立即发起请求并返回一个未完成的 Promise。then 方法中传入的回调函数会在请求完成时被调用,而 catch 会捕获任何可能发生的错误。
例如:
javascript
fetch(url) // 发起请求并返回一个 Promise
.then(response => {
console.log("Response received");
return response.json(); // 解析 JSON 数据
})
.catch(error => console.log(error)); // 捕获错误
在浏览器中,fetch 实际上是通过 JavaScript 事件循环机制调度 HTTP 请求,它不会阻塞主线程。由于 fetch 是异步的,它允许页面在等待请求返回的同时执行其他任务。
2.3 请求头与配置
在 fetch 请求中,你可以通过 options 对象来配置请求。例如,你可以设置请求方法(GET、POST 等),设置请求头,发送请求体等。
javascript
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' }) // 请求体
};
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log(error));
这些选项会通过浏览器的 HTTP 客户端 发送到服务器。浏览器会根据请求的类型和配置,构建相应的 HTTP 请求头部和请求体。
2.4 响应处理
fetch 发起请求并接收响应时,它返回的 response 对象代表 HTTP 响应。这些响应信息包括状态码、响应头和响应体内容。
javascript
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // 返回 JSON 数据
})
.then(data => console.log(data))
.catch(error => console.log(error));
response.ok 是一个布尔值,表示响应是否成功(状态码 200 到 299 表示成功)。response.json()、response.text()、response.blob() 等方法允许你读取响应体内容。
2.5 CORS(跨域资源共享)
当你发起跨域请求时,浏览器会处理跨域资源共享(CORS)问题。fetch 会自动处理 预检请求(OPTIONS 请求),并根据目标服务器的 CORS 策略决定是否允许访问。
如果服务器响应了正确的 CORS 头部,浏览器会允许访问并返回资源。
如果服务器未响应适当的 CORS 头部,fetch 请求将被阻止,并抛出一个错误。
3. 底层实现
从实现角度来看,fetch 依赖于浏览器内部的网络通信堆栈。以下是 fetch 实现的几个关键点:
请求建立:
当 fetch 被调用时,浏览器会通过其网络库(如 libcurl 或原生的浏览器网络栈)解析 URL、进行 DNS 查询、建立 TCP 连接。
发送请求:
浏览器根据配置(如请求方法、头部、体等)构造 HTTP 请求并发送到目标服务器。
等待响应:
请求发送后,浏览器会等待响应。浏览器根据响应的 HTTP 状态码和头部信息来判断请求是否成功。
解析响应:
浏览器收到响应后,会将响应内容存储在 response 对象中,并提供如 response.json()、response.text() 等方法来解析响应内容。
事件循环和 Promise:
fetch 的操作是异步的,基于 事件循环 机制和 Promise。当响应可用时,相关的 .then() 或 .catch() 方法会被执行。
4. 与 XMLHttpRequest 的对比
fetch 与传统的 XMLHttpRequest (XHR) 相比,有以下几个主要优势:
Promise 支持:fetch 基于 Promise,使得代码更加简洁和易于理解,避免了回调地狱。
简洁的 API:fetch 的 API 更加简洁,尤其是在处理响应体时。你只需要使用 .json()、.text()、.blob() 等方法来解析响应内容,而 XMLHttpRequest 需要更多的手动配置。
默认不发送 Cookies:fetch 默认不会发送跨域请求时的 Cookies(除非配置了 credentials 选项)。而 XMLHttpRequest 默认会自动发送 Cookies。
支持流式传输:fetch 支持响应流(ReadableStream),允许更高效地处理大文件和流式响应。
5. 总结
fetch 是现代浏览器中用来发起 HTTP 请求的原生 API,它依赖浏览器的网络堆栈来执行低级别的请求操作。通过基于 Promise 的设计,fetch 提供了更清晰的异步操作流程。它的实现利用了浏览器的网络层和异步编程机制,能够有效处理 HTTP 请求、响应、CORS 等问题。
7. fetch和XMLHttpRequest区别
fetch 和 XMLHttpRequest 都是浏览器提供的用于发起 HTTP 请求的 API,尽管它们的目的相同------发送和接收数据------它们在设计、功能、使用方式和底层实现上有许多差异。下面是它们之间的关系和主要区别:
1. 关系:
功能相似:fetch 和 XMLHttpRequest 都用于从客户端(浏览器)发送 HTTP 请求到服务器,并获取响应。这意味着它们都可以用来发送数据(如表单提交、获取 API 数据等)。
底层原理:虽然 fetch 是现代浏览器的标准 API,而 XMLHttpRequest 是较旧的标准 API,但它们都通过浏览器的网络堆栈与服务器通信。fetch 是 XMLHttpRequest 的替代品,提供了更现代、更简洁的 API。
异步操作:fetch 和 XMLHttpRequest 都是异步的,可以执行异步 HTTP 请求,但 fetch 使用了更现代的 Promise 机制,而 XMLHttpRequest 主要基于回调(callback)机制。
2. 主要区别:
2.1 API 风格和易用性
fetch:
基于 Promise,支持链式调用,使代码更加简洁和易于维护。
语法简洁,只返回响应的 response 对象,不会自动处理请求错误(需要手动检查状态码和抛出错误)。
采用更现代的设计,支持 async/await,使得处理异步操作更自然。
示例:
javascript
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
XMLHttpRequest:
基于回调机制,使用 onreadystatechange 或 onload 事件处理响应,导致代码更复杂,容易出现回调地狱(callback hell)。
没有内置的 Promise 支持,因此通常与回调函数或外部库(如 jQuery)一起使用来简化操作。
示例:
javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
xhr.send();
2.2 请求和响应的处理
fetch:
返回的是一个 Promise,这意味着响应是异步处理的,并且通过 then() 或 async/await 进行响应数据的处理。
不会自动抛出 HTTP 错误(如 404 或 500),需要手动检查 response.ok 或 response.status 来判断请求是否成功。
XMLHttpRequest:
使用事件回调处理响应,通常是通过 onload 或 onreadystatechange 来处理请求的响应。
自动将响应文本(responseText)作为字符串返回,解析 JSON 或其他数据类型需要手动处理。
XMLHttpRequest 会自动在请求失败时调用 onerror 回调函数。
2.3 错误处理
fetch:
默认不会对 HTTP 错误(如 404、500)抛出错误,只有网络错误(例如无网络连接)才会触发 .catch()。
需要手动检查 response.ok 或 response.status 来判断响应是否成功。
示例:
javascript
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Fetch error:', error));
XMLHttpRequest:
XMLHttpRequest 会根据请求的状态码来执行相应的回调方法,通常是通过检查 xhr.status 来判断请求是否成功。
2.4 请求体处理
fetch:
直接支持通过 body 选项发送请求体(如发送 JSON 数据),且支持 FormData、Blob、ArrayBuffer 等多种数据类型。
通过 headers 选项配置请求头。
示例:
javascript
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' })
};
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data));
XMLHttpRequest:
需要手动设置请求头,并使用 xhr.send() 发送请求体。
对发送 JSON 数据通常需要手动字符串化,并设置 Content-Type 头部。
示例:
javascript
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ key: 'value' }));
2.5 跨域请求(CORS)
fetch:
默认支持跨域请求(CORS),但需要服务器在响应中包含适当的 CORS 头部。
可以通过 credentials 选项来控制是否发送 Cookies、授权头等。
示例:
javascript
fetch(url, { credentials: 'same-origin' })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
XMLHttpRequest:
支持跨域请求,但需要手动处理 CORS(通常在跨域时需要服务器添加 CORS 头部)。
默认发送跨域请求时不会携带 Cookies。
2.6 流式响应(Streams)
fetch:
支持流式响应,允许逐步处理大文件或大数据集。响应体是一个 ReadableStream,这使得你可以逐步读取数据而不是一次性获取整个响应。
示例:
javascript
fetch(url)
.then(response => {
const reader = response.body.getReader();
reader.read().then(function processText({ done, value }) {
if (done) return;
console.log(value);
reader.read().then(processText);
});
});
XMLHttpRequest:
不支持流式响应。它需要等待整个响应下载完毕才会调用回调函数。
2.7 支持情况
fetch:
fetch 是一个现代的 API,已被大多数现代浏览器(如 Chrome、Firefox、Edge、Safari)支持,但在一些旧版本的浏览器中(如 Internet Explorer)不支持。
需要通过 polyfill 来在不支持的浏览器中使用。
XMLHttpRequest:
XMLHttpRequest 在所有主流浏览器中都被广泛支持,包括旧版本的浏览器(如 Internet Explorer)。
3. 总结
fetch 是一个现代的、基于 Promise 的 API,具有更简洁的语法和更强大的功能,如流式响应、异步操作的简化、更易于使用的错误处理等。
XMLHttpRequest 是较老的 API,基于回调机制,功能上不如 fetch 灵活和简洁,但仍然被广泛使用,特别是在一些较旧的浏览器中。
尽管 fetch 是 XMLHttpRequest 的现代替代品,XMLHttpRequest 仍然存在于许多遗留的代码中。fetch 是未来的主流选择,而 XMLHttpRequest 在许多场景下也依然可用,尤其是与旧版本的浏览器兼容时。
8. Axios的实现原理
Axios 是一个基于 Promise 的 JavaScript 库,用于简化浏览器和 Node.js 环境中的 HTTP 请求。Axios 主要通过封装 XMLHttpRequest 或 Node.js 的 http/https 模块 来实现对 HTTP 请求的支持,而 并没有直接封装 fetch。
1. 浏览器中的实现
在 浏览器环境 中,Axios 默认使用 XMLHttpRequest 进行 HTTP 请求。XMLHttpRequest 是浏览器中较早的一个用于发起 HTTP 请求的 API,广泛支持,并且可以处理浏览器中常见的 HTTP 请求需求。
使用 XMLHttpRequest:
在浏览器环境中,Axios 封装了 XMLHttpRequest,对其进行了改造和增强,使得 API 使用更加简洁。
通过封装,Axios 提供了基于 Promise 的异步编程模型,支持链式调用、请求和响应拦截、请求取消、自动转换 JSON 数据等功能。
Axios 还会自动处理一些常见的错误情况(如 404、500 错误)并通过 Promise 抛出异常,方便开发者统一处理。
示例:Axios 在浏览器中的基本实现(简化版)
javascript
// 伪代码,简化版
function axiosRequest(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(config.method, config.url, true);
// 设置请求头
Object.keys(config.headers).forEach(key => {
xhr.setRequestHeader(key, config.headers[key]);
});
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response); // 请求成功,解析响应
} else {
reject(new Error('Request failed with status code ' + xhr.status)); // 请求失败
}
};
xhr.onerror = function () {
reject(new Error('Network Error')); // 网络错误
};
// 发送请求
xhr.send(config.data);
});
}
2. Node.js 中的实现
在 Node.js 环境 中,Axios 不是使用 XMLHttpRequest,因为 XMLHttpRequest 是浏览器专有的 API,而是使用 Node.js 原生的 http 和 https 模块 来发起 HTTP 请求。
使用 http/https 模块:
在 Node.js 中,Axios 使用 Node.js 自带的 HTTP 库 (http 或 https),这些库非常适合用于服务器端请求。
Axios 将 HTTP 请求和响应处理的过程封装为 Promise,允许开发者通过链式调用来处理异步操作。
示例:Axios 在 Node.js 中的基本实现(简化版)
javascript
// 伪代码,简化版
const http = require('http');
const https = require('https');
function axiosRequest(config) {
return new Promise((resolve, reject) => {
const client = config.url.startsWith('https') ? https : http;
const req = client.request(config.url, { method: config.method, headers: config.headers }, (res) => {
let data = '';
// 接收数据
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(data); // 请求成功,解析响应
} else {
reject(new Error('Request failed with status code ' + res.statusCode)); // 请求失败
}
});
});
req.on('error', (e) => {
reject(e); // 网络错误
});
// 发送请求
req.write(config.data);
req.end();
});
}
3. Axios 与 fetch 的关系
Axios 和 fetch 都是基于 Promise 的异步 API,功能上有很多相似之处,例如:
都支持发起 GET、POST 等 HTTP 请求。
都支持 Promise,允许链式调用。
都允许设置请求头、请求体、超时等配置。
然而,Axios 并没有直接基于 fetch 封装。虽然 fetch 是现代浏览器中用于发起 HTTP 请求的推荐 API,Axios 仍然选择使用 XMLHttpRequest 在浏览器中发起请求,原因如下:
更广泛的兼容性:XMLHttpRequest 在较旧的浏览器中也有很好的支持,尤其是在像 Internet Explorer 这样的旧版浏览器中。fetch 相对较新,部分旧浏览器不支持,需要 polyfill 来补充支持。
功能扩展:Axios 提供了一些 fetch 不直接支持的功能,比如请求拦截器、响应拦截器、自动转换 JSON 数据、请求取消、默认超时处理等。
简化错误处理:fetch 本身不会自动抛出 HTTP 错误(如 404 或 500 错误),需要开发者手动检查 response.ok。而 Axios 会根据 HTTP 状态码自动抛出异常,简化了错误处理。
4. 总结
Axios 的实现原理:Axios 在 浏览器中使用 XMLHttpRequest,在 Node.js 中使用 http 或 https 模块 来发起 HTTP 请求。
Axios 并没有封装 fetch,尽管它与 fetch 有一些相似之处。Axios 提供了更多的功能(如请求拦截、响应拦截、自动 JSON 转换等),并选择了在浏览器中使用 XMLHttpRequest 来确保更广泛的兼容性。