前端网络请求的前世今生:从 Ajax、XHR 到 Fetch 和 Axios

Ajax、XHR、Fetch、Axios:你以为你懂,其实可能搞混了

这几个概念看着眼熟,可真要讲清楚区别,很多人就会越说越乱,最后把自己都绕进去了。

上图用于帮你理清四者的关系和时间线。接下来我们展开聊聊------很多人被问到"Ajax和XHR有什么区别时"会愣住,不是因为不懂,而是从来没想过Ajax压根不是一个API


一、先解释一个最常见的误解

很多人以为 Ajax 是一个像 fetch() 那样可以直接调用的函数,或者是一个浏览器内置对象,但其实并不是这个样子。

Ajax(Asynchronous JavaScript and XML)本质上是一种编程模式(或者说编码理念),而不是某个具体的技术、协议或 API。 它的核心思想是"在不刷新整个页面的情况下,与服务器交换数据并更新部分页面"。它诞生于 2005 年,Jesse James Garrett 在一篇文章里给它起了这个名字。真正让 Ajax 火出圈的是 Google Maps 和 Gmail------用户发现网页居然可以像桌面应用一样流畅交互------这在今天看来稀松平常,但在当时确实是跨时代的进步。

Ajax 本身并没有提供任何 API 和 具体实现,它的底层实现,靠的是浏览器内置的 XMLHttpRequest 对象(简称 XHR)。

一句话:Ajax 是理念,XHR 是实现------前者回答"要做什么",后者回答"怎么做到"。

这时候可能有人会问:那 Fetch 和 Axios 呢?它们不也是做异步请求的吗?和 Ajax 是什么关系?

答案是:Fetch 和 Axios,本质上都是实现 Ajax 这套理念的"工具"------和 XHR 是同一层级的东西,只是走的路子不一样。Fetch 是浏览器原生提供的另一种底层通信方式,用来替代 XHR;Axios 则是一个第三方 HTTP 客户端库,在浏览器环境下,它内部其实也是靠 XHR 来发请求的。


二、为什么有了 XHR,还要有 Fetch?有了 Fetch,还要有 Axios?

技术演进从来不是"后出现的全面碾压前一个",而是新方案解决了旧方案的痛点,但也带来了新的问题

2.1 XHR:功高盖主,但写法劝退

XHR 最早是 1999 年微软在 IE5 里以 ActiveX 控件的形式引入的,目的是给 Outlook Web Access 做异步数据交互。后来 W3C 把它标准化,所有主流浏览器都支持了。

它的经典写法长这样:

javascript 复制代码
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/user', true);

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(JSON.parse(xhr.responseText));
    } else {
      console.error('出错了', xhr.statusText);
    }
  }
};

xhr.send();

readyState 有 5 个状态(0~4),onreadystatechange 事件会在每次状态变化时触发。这种"事件驱动 + 状态机"的模式在 2000 年代很合理,但放到 Promise 和 async/await 普及的今天,就显得有点啰嗦了。

而且 XHR 的 API 设计也不太符合现代开发习惯------配置、发送、监听、错误处理都集中在一个对象上。

一句话:XHR 是浏览器网络请求的"万能插座",但插上去的过程有点繁琐。

2.2 Fetch:Promise 时代的原生答案

2015 年,ES6 带来了 Promise,浏览器也顺势推出了 Fetch API。它的设计目标很简单:提供一个基于 Promise 的、更现代的原生 HTTP 请求 API

最简版本:

javascript 复制代码
fetch('https://api.example.com/user')
  .then(res => res.json())
  .then(data => console.log(data));

配合 async/await 后更加清爽:

javascript 复制代码
const res = await fetch('https://api.example.com/user');
const data = await res.json();

看起来很美,对吧?但事情没那么简单。

Fetch 有几个"坑"是很多人踩过的:

  • HTTP 错误不会自动进 catch :404 或 500 返回的 Promise 依然通常是 fulfilled,你需要手动判断 res.ok
  • 跨源 Cookie 默认不会随请求发送 :跨源场景通常需要显式设置 credentials: 'include'
  • 超时需要结合取消机制实现 :现代 Fetch 通常使用 AbortController / AbortSignal 处理超时和取消。
  • 进度监控相对复杂 :XHR 有 onprogress 等事件,Fetch 则通常需要结合 ReadableStream 自己读取数据并计算进度。
  • IE 完全不支持:虽然 IE 已经退出历史舞台,但当年这是一个实打实的兼容性问题。

所以 Fetch 更像是一个"底层、通用、现代"的原语,而不是"开箱即用"的完整 HTTP 客户端。

一句话:Fetch 给了你一个更优雅的起点,但走到生产环境,你会发现一些工程能力需要自己补齐。

2.3 Axios:不是 Fetch 的升级版,而是另一条路

Axios 诞生于 2014 年,真正火起来是在 Vue/React 生态爆发之后。这里有个关键认知:

Axios 不是 Fetch 的"高级封装",它和 Fetch 是平行关系。

维度 Fetch Axios
本质 浏览器原生标准 API 第三方 HTTP 客户端库
浏览器端底层 浏览器原生 Fetch 实现 主要使用 XHR,也可使用其他适配器
Node.js 现代 Node.js 已原生提供 Fetch 可使用 Node.js HTTP 等适配器
Promise 原生支持 原生支持
超时控制 结合 AbortSignal 等机制 提供 timeout 等配置
拦截器 没有传统意义上的内置拦截器 内置请求/响应拦截器
自动 JSON 转换 需要调用 res.json() 等方法 自动转换常见 JSON 响应
HTTP 错误处理 默认不因 4xx/5xx 自动 reject 默认可按状态码策略 reject
请求取消 AbortController / AbortSignal 支持 AbortController
浏览器 + Node.js 现代运行环境均可使用 浏览器 + Node.js

Axios 的核心价值在于:它把日常开发中的大量重复劳动(设置 baseURL、加请求头、错误统一处理、JSON 转换、请求取消等)通过统一 API 和拦截器机制封装起来。

javascript 复制代码
// 创建一个 axios 实例,全局配置一次
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: { 'X-Custom-Header': 'foobar' }
});

// 请求拦截器:每次发请求前自动加 token
api.interceptors.request.use(config => {
  config.headers.Authorization =
    `Bearer ${localStorage.getItem('token')}`;
  return config;
});

// 响应拦截器:统一处理错误
api.interceptors.response.use(
  res => res.data,
  err => {
    if (err.response?.status === 401) {
      // 统一跳转登录
    }
    return Promise.reject(err);
  }
);

// 使用时简洁到离谱
const user = await api.get('/user');

一句话:Axios 是一个"为开发者体验优化"的 HTTP 客户端,它不是任何原生 API 的替代品,而是工程化的请求工具。


三、底层原理:它们到底怎么把请求发出去的?

聊完"为什么有它们",我们再往下探一层------在浏览器里,当这些 API 发起请求后,酒精发生了些什么?

3.1 XHR 的完整链路

当你写下 xhr.send() 时,浏览器内部大致经历这样一套流程:

text 复制代码
JS 执行环境
  ↓ 创建 XMLHttpRequest 对象
  ↓ 配置请求参数 (open)
  ↓ 注册回调 (onreadystatechange / onload / onerror)
  ↓ send() ------ 将请求交给浏览器网络栈

浏览器网络层
  ↓ DNS 解析
  ↓ 建立或复用网络连接
  ↓ 发送 HTTP 请求
  ↓ 等待服务端响应
  ↓ 接收响应数据(响应头 → 响应体)

浏览器
  ↓ 将网络事件/任务调度回 JavaScript 执行环境
  ↓ 事件循环调度
  ↓ 执行你的 onreadystatechange / onload 回调

需要注意:现代浏览器的进程模型和网络实现比上面的简化流程复杂得多,不能简单理解成固定的"JS 主线程 → 网络进程 → IPC → 主线程"单一路径。

XHR 的 readyState 五个状态,本质上反映了请求对象所处的生命周期阶段:

readyState 含义 阶段
0 UNSENT 对象刚创建,open() 还没调用
1 OPENED open() 已调用,请求已配置
2 HEADERS_RECEIVED 已收到响应头
3 LOADING 正在接收响应体
4 DONE 请求完成

这也是为什么 XHR 能提供比较直接的进度监控 (如 onprogress)和同步模式(虽然同步 XHR 会阻塞执行环境,现代 Web 开发通常不应该使用)。

3.2 Fetch 的底层有什么不同?

Fetch API 并不是"在 XHR 外面套了一层 Promise"。从 Web 平台标准和浏览器实现角度看,Fetch 与 XHR 是不同的 API 抽象,它们最终都可以进入浏览器的网络栈,但并不存在一个简单的"Fetch → XHR"固定调用关系。

Fetch 的设计和 Request / Response / Headers / Streams / Service Worker 等 Web 平台能力紧密相关。

它的请求流程可以粗略理解为:

text 复制代码
fetch(url)
  ↓ 创建/规范化 Request
  ↓ 按 Fetch 标准执行请求流程
  ↓ Service Worker(如果满足条件并被拦截)
  ↓ 浏览器网络层
  ↓ Promise 得到 Response
  ↓ Response.body 可以作为 ReadableStream 使用
  ↓ 调用 .json() / .text() 等方法消费响应体

关键区别在于:Fetch 返回的 Response 并不意味着响应体已经全部读取完成。响应头到达后,就可以得到 Response,之后再通过 response.body.json().text() 等方式消费响应体。

这也是 Streams API 的重要意义之一:应用可以逐步处理响应数据,而不是只能等待整个响应体一次性拿到。

而 Fetch 的取消机制通常使用 AbortController / AbortSignal

javascript 复制代码
const controller = new AbortController();

const response = await fetch('/api/user', {
  signal: controller.signal
});

// 取消请求
controller.abort();

3.3 Axios 的底层:它到底调了什么?

Axios 在浏览器端的经典实现路径主要是 XHR;具体底层适配器会随 Axios 版本和运行环境变化,因此不应该简单概括成"Axios 永远就是 XHR"。

在浏览器中,可以把它理解成:

text 复制代码
你的业务代码
    ↓ 调用 axios.get('/api')
Axios
    ↓ 合并配置(baseURL、headers、params 等)
    ↓ 执行请求拦截器
    ↓ 选择浏览器端请求适配器
    ↓ 浏览器环境下通常使用 XHR
    ↓ 接收响应
    ↓ 执行响应拦截器
    ↓ 返回 AxiosResponse / 业务数据

而在 Node.js 环境中,Axios 会根据适配器使用 Node.js 对应的网络能力。

这就是为什么 Axios 能够在浏览器和 Node.js 等不同运行环境中提供相对统一的调用方式。

一句话总结这一节:XHR 和 Fetch 是不同的 Web 平台请求 API;Axios 则是在这些底层能力之上提供统一配置、拦截器、转换和错误处理等工程化能力的 HTTP 客户端。


四、实际项目中,到底怎么选?

聊到这里,你可能已经有点感觉了。但在实际项目中如果只是无脑使用 Axios 是不够的,具体的选择取决于场景

场景 1:写一个极简的 Demo 或脚本

javascript 复制代码
const data = await (await fetch('/api/ping')).json();

Fetch 的原生、无额外依赖在这里就是优势。

场景 2:企业级前端项目(Vue/React/Angular)

可以考虑 Axios,也可以直接基于 Fetch 封装一层自己的请求客户端。

选择 Axios 的常见原因:

  • 拦截器:统一加 token、统一处理 401,不用在每个组件里重复写。
  • 超时控制:统一处理请求超时。
  • 自动 JSON 转换:减少重复的响应解析代码。
  • 请求取消 :结合 AbortController 管理不再需要的请求。
  • 统一实例配置 :集中管理 baseURL、headers、参数序列化等。

但也不要把 Axios 当成"企业项目必选项"。现代前端完全可以基于 Fetch 封装出一套满足项目需求的请求层。

场景 3:需要上传/下载进度条

XHR 原生支持 onprogress、上传进度等能力,Axios 也可以暴露相应能力:

javascript 复制代码
axios.get('/large-file', {
  onDownloadProgress: (e) => {
    if (e.total) {
      const percent = Math.round((e.loaded / e.total) * 100);
      console.log(`下载进度: ${percent}%`);
    }
  }
});

Fetch 同样可以做,但通常需要读取 ReadableStream 并自行计算进度,代码和处理复杂度会更高。

场景 4:兼容老旧环境

如果项目还要支持 IE11,Fetch 原生不可用。这时候可以使用 XHR,或者选择能够兼容目标环境的第三方方案。

需要注意的是,不能简单说"Axios 自动兼容所有老浏览器"。实际兼容性还取决于 Axios 版本、构建目标以及运行环境。

场景 5:学习或面试

手写一个 Ajax 请求是经典面试题。这时候你需要理解 XHR 的完整生命周期------opensendreadyStatestatusresponseText,一个都不能少。

javascript 复制代码
function myAjax(url, method = 'GET', data = null) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.open(method, url, true);

    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(xhr.response);
        } else {
          reject(new Error(xhr.statusText));
        }
      }
    };

    xhr.onerror = () => reject(new Error('网络请求失败'));

    xhr.send(data);
  });
}

一句话:Fetch 适合"轻量、原生、无额外依赖"的场景;Axios 适合"工程化、统一封装、功能丰富"的场景;XHR 更适合学习底层机制、兼容特殊环境以及某些需要直接使用 XHR 能力的场景。


五、一张图 + 几句话,带走核心认知

如果你看完只记得几件事,那就记住这些:

概念 它到底是什么 一句话定位
Ajax 技术方案/编程模式 "异步局部更新页面"的思想,不是某个具体 API
XHR 浏览器 Web API 经典的异步网络请求 API,事件驱动,生命周期状态明显
Fetch 浏览器 Web API 现代标准网络请求 API,基于 Promise,并与 Streams 等能力结合
Axios 第三方 HTTP 客户端库 对底层请求能力进行工程化封装,提供拦截器、统一配置等能力

它们的关系不是简单的"谁替代谁",而是处于不同层次

  • Ajax 是一种开发思想/模式
  • XHR 和 Fetch 是浏览器提供的两套请求 API
  • Axios 是第三方 HTTP 客户端库

最后送一个面试小技巧:如果面试官问"Fetch 和 Axios 有什么区别",不要说"Axios 是 Fetch 的高级版"------这是不准确的。

更好的回答是:

"Fetch 是浏览器原生的标准 Web API,设计偏底层和通用;Axios 是第三方 HTTP 客户端库,在浏览器端主要基于 XHR,并提供了统一配置、拦截器、转换和错误处理等工程化能力。两者不是简单的上下级关系,也不存在 Axios 就是 Fetch 封装这一说。"


这篇文章如果对你有帮助,欢迎点赞收藏。你在项目里更常用 Fetch 还是 Axios?遇到过什么坑?评论区聊聊。

相关推荐
七牛开发者12 分钟前
拆解 dsh:Turn 与 Step 如何组织 Agent 主循环
前端·javascript·人工智能
梨想橙汁13 分钟前
JavaScript 零基础入门:引入方式、变量与数据类型,吃透原始与引用类型
前端·javascript
WebInfra26 分钟前
Rslib 1.0 正式发布:面向多场景的 JavaScript 库开发工具
前端·javascript·github
汉堡大王95271 小时前
面试官:讲讲归并排序 —— 为什么它能在面试里反复出现?
前端·javascript·面试
kyriewen1 小时前
别再只给人写页面了:AI 已经开始自己点你的按钮、填你的表单
前端·javascript·人工智能
天天喝旺仔1 小时前
Vue 3 组合式 API:从 Options 迁移到 script setup
前端·javascript·vue.js
meilindehuzi_a2 小时前
LangChain.js 对话 Memory 实战:History 持久化、截断与摘要压缩
java·javascript·langchain
江畔柳前堤2 小时前
具身智能全景深度指南(2026年9月版):从“会聊天的AI“到“能干活的机器“
大数据·javascript·图像处理·人工智能·分布式·智慧城市·原型模式
liuyicenysabel2 小时前
Grafana + Prometheus 分级告警配置设计(P0/P1/P2)
javascript·grafana·prometheus