创见博客
AJAX、Fetch、XHR 到底啥区别:一个概念,两代 API
七崽爱吃小饼干2026/10/12阅读 0

这三个词几乎天天在面试题和文档里一起出现,很多人却把它们当成三个并列的东西,混着用。其实它们根本不在一个层级:

text
AJAX   = 一种「不刷新页面就能请求数据」的思想
XHR    = 实现这个思想的第一个 API(XMLHttpRequest)
Fetch  = 实现这个思想的第二个 API(fetch)

AJAX 是概念,XHR 和 Fetch 是它的两代实现。 把这句话记住,剩下都是细节。

一、AJAX:它压根不是一个 API

AJAX 全称 Asynchronous JavaScript and XML,是 2005 年 Jesse James Garrett 在一篇文章里造出来的词,用来形容 Gmail、Google Maps 展示的那种体验:页面不重新加载,就能偷偷跟服务器换数据。

所以 AJAX 描述的是一种做法,不是一个能 new 出来的对象:

text
用户操作 → JS 在后台发请求 → 拿回数据 → 只更新页面的一小块
(整个过程页面不刷新)

有个历史遗留点值得说:名字里的 X(XML) 早就名不副实了。今天 AJAX 传的数据 99% 是 JSON,但这个词照用。所以你现在看到「AJAX 请求」,把它理解成「前端异步请求」就行,它不特指任何一个 API。

二、XHR:第一代实现

XHR,全称 XMLHttpRequest。它才是真正干活的第一个浏览器 API——1999 年随 IE5 出现,后来被标准化。AJAX 那篇文章能火,靠的就是它。

XHR 是事件驱动 + 回调的风格:

javascript
const xhr = new XMLHttpRequest()

xhr.open('GET', '/api/user')
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {          // 4 = 请求完成
    if (xhr.status >= 200 && xhr.status < 300) {
      console.log(JSON.parse(xhr.responseText))
    }
  }
}

xhr.timeout = 5000                      // 原生支持超时
xhr.onprogress = e => console.log(e.loaded, e.total)  // 原生支持进度
xhr.onerror = () => console.error('网络错误')
xhr.abort()                             // 原生支持取消

xhr.send()

它的特点是啰嗦:readyState、回调嵌套、手动解析 JSON,一层套一层就成「回调地狱」。

三、Fetch:第二代实现

Fetch 是 Promise 风格的现代 API,属于 WHATWG 的 Fetch 标准。写法干净得多:

javascript
async function getUser() {
  const res = await fetch('/api/user')

  if (!res.ok) throw new Error(`HTTP ${res.status}`)  // 必须手动判!见下文

  const data = await res.json()
  return data
}

await 一行搞定,天然配合 async/await,还支持流式读取响应体。看起来是全面升级。

但——Fetch 不是 XHR 的严格超集,它砍掉了一些能力,这也是它最大的坑。

四、核心差异表

维度XHRFetch
出现时间19992015 左右
编程模型事件 + 回调Promise / async-await
请求失败时onerror(仅网络错误)Promise reject(仅网络错误)
HTTP 4xx/5xx需手动查 status也 resolve,需手动查 res.ok
超时原生 xhr.timeout无,要 AbortController
上传进度原生 onprogress无标准方案
下载进度原生 onprogress需读 response.body 流
取消请求xhr.abort()AbortController
流式响应不支持支持 ReadableStream
同源带 Cookie默认带默认带(credentials: 'same-origin')
跨域带 CookiewithCredentials = truecredentials: 'include'

最需要记的一条:Fetch 遇到 404、500 照样 resolve,不会进 catch。 它只在网络层失败(断网、DNS 错、被 CORS 拦)时才 reject。所以 res.ok 那句判断绝对不能省,新手翻车多半翻在这。

五、Fetch 的坑:为什么很多人还在用 XHR

Fetch 看着先进,但这几个短板让它在某些场景反而不如 XHR:

  • 不 reject HTTP 错误:400/500 都得自己判 res.ok,容易漏。
  • 没有超时:得自己配 AbortController + setTimeout,写起来一点都不优雅。
javascript
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 5000)

try {
  const res = await fetch('/api/slow', { signal: controller.signal })
} finally {
  clearTimeout(timer)
}
  • 没有上传进度:传大文件想看进度条,Fetch 目前没有标准方案,XHR 的 onprogress 才是现成的。
  • 老环境不认:极老的浏览器、某些旧 Node 版本需要 polyfill。

六、别把 axios 也算进来

很多人把 axios 当成「第三种方案」,其实它是XHR 的封装库(浏览器端底层就是 XHR,Node 端是 http 模块)。它不改变 AJAX 的本质,只是补齐了 Fetch 缺的东西:

text
axios = XHR + Promise + 超时 + 拦截器 + 自动 JSON + 统一错误处理

拦截器(统一加 token、统一处理 401)是它的杀手锏,Fetch 想做到同样效果得自己包一层。

七、那到底怎么选

text
新项目、普通接口请求          → fetch(原生、够轻)
需要上传进度 / 拦截器 / 老兼容 → axios
维护老代码 / 需要 onprogress  → XHR
只想改名、其实没换实现        → 警惕:axios 底层还是 XHR

一句话:能用 Fetch 就用 Fetch,需要它没有的能力再上 axios,裸写 XHR 基本只剩维护老代码这一种理由。

小结

  • AJAX 是思想(不刷新页面异步取数据),2005 年造的词,名字里的 XML 已经过时。
  • XHR 是第一代实现:事件回调风格,啰嗦,但原生带超时、进度、取消。
  • Fetch 是第二代实现:Promise 风格,简洁、支持流式,但不带超时、上传进度,且 4xx/5xx 不会 reject。
  • axios 不是第三种方案,它是 XHR 的封装。
  • 层级关系:AJAX ⊃ { XHR, Fetch };前者是概念,后两者是实现。
  • 选型口诀:普通请求用 Fetch,要进度/拦截器用 axios,裸 XHR 多是遗产代码。
评论
0/100