XMLHttpRequest、AJAX、Fetch 与 Axios

在现代 Web 开发中,前端与后端的数据交互是构建动态应用的核心。围绕这一需求,诞生了多个关键技术与工具:XMLHttpRequest(XHR)AJAXAxiosFetch API。它们之间既有历史演进关系,也有功能重叠与互补。本文将系统梳理四者的关系,深入剖析 XHR 的工作机制与 Fetch 的底层原理,并结合 Vue 3 开发实践,提供一套完整的前端网络通信知识体系。


一、核心概念与层级关系

1.1 AJAX:一种编程范式(不是技术)

  • 全称:Asynchronous JavaScript and XML
  • 本质一种开发模式,指在不刷新页面的情况下,通过 JavaScript 异步与服务器交换数据并更新部分网页内容。
  • 核心思想:解耦 UI 更新与数据获取,提升用户体验。

关键点

AJAX 不是某个具体 API,而是一种使用现有技术实现异步通信的策略

实现 AJAX 的核心技术就是 XMLHttpRequest

1.2 XMLHttpRequest(XHR):浏览器原生 API

  • 角色实现 AJAX 的底层工具

  • 功能:提供浏览器与服务器进行 HTTP 通信的能力

  • 特点

    • 基于回调(事件驱动)
    • 支持进度监控、取消请求、上传/下载
    • 兼容性极好(IE7+)

📌 关系
XHR 是 AJAX 的"引擎" 。没有 XHR,就没有现代意义上的 AJAX。

1.3 Axios:基于 Promise 的 HTTP 客户端库

  • 定位对 XHR 的封装与增强

  • 核心特性

    • 返回 Promise,支持 async/await
    • 自动转换 JSON 数据
    • 拦截器(请求/响应)
    • 客户端支持 XSRF 防护
    • 浏览器 + Node.js 双端支持
  • 底层实现 :在浏览器中默认使用 XHR ,在 Node.js 中使用 http 模块

scss 复制代码
// Axios 内部简化逻辑
function axios(config) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(config.method, config.url);
    xhr.send(config.data);
    xhr.onload = () => resolve(xhr.response);
    xhr.onerror = () => reject(xhr.statusText);
  });
}

关系
Axios 是 XHR 的现代化封装,让开发者用更简洁的语法享受 XHR 的全部能力。

1.4 Fetch API:浏览器新一代原生 API

  • 定位XHR 的官方继任者

  • 设计目标

    • 基于 Promise,符合现代 JS 编程习惯
    • 更简洁的 API 设计
    • 更好的流(Stream)支持
    • 统一请求/响应模型(Request/Response 对象)
  • 底层实现并非基于 XHR ,而是直接调用浏览器的网络层(如 Chromium 的 blink::WebURLLoader

⚠️ 重要区别

Fetch 不是 XHR 的封装 ,而是全新的底层实现


二、四者关系图谱

scss 复制代码
                          ┌──────────────┐
                          │    AJAX      │ ←── 编程范式(异步通信思想)
                          └──────┬───────┘
                                 │
         ┌───────────────────────┼───────────────────────┐
         │                       │                       │
┌────────▼────────┐   ┌──────────▼──────────┐   ┌────────▼────────┐
│ XMLHttpRequest  │   │       Fetch API     │   │      Axios      │
│ (原生, 回调式)   │   │ (原生, Promise式)   │   │ (第三方库, Promise)│
└────────┬────────┘   └─────────────────────┘   └────────┬────────┘
         │                                               │
         └───────────────────────┬───────────────────────┘
                                 │
                   ┌─────────────▼─────────────┐
                   │   现代 Web 应用数据通信    │
                   └───────────────────────────┘

🔑 总结关系

  • AJAX 是思想,XHR/Fetch 是实现该思想的原生工具
  • Axios 是对 XHR(浏览器端)的高级封装
  • Fetch 是浏览器提供的、与 XHR 并列的新一代原生 API

三、XMLHttpRequest 详解

3.1 基本使用流程

ini 复制代码
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(xhr.responseText);
  }
};
xhr.send();

3.2 核心属性

属性 说明
readyState 请求状态(0--4)
status / statusText HTTP 状态码与描述
responseText 字符串响应体
response 根据 responseType 解析后的数据
responseType 响应类型(jsonblobarraybuffer 等)

3.3 事件模型

  • 传统方式onreadystatechange(需手动判断 readyState

  • 现代方式(推荐):

    • onload:请求完成
    • onerror:网络错误
    • ontimeout:超时
    • onabort:被中止

3.4 高级功能

  • 超时控制xhr.timeout = 5000
  • 跨域凭据xhr.withCredentials = true
  • 上传进度xhr.upload.onprogress
  • 中止请求xhr.abort()

3.5 实际应用场景

文件上传(带进度)

ini 复制代码
function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);
  
  const xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload');
  
  xhr.upload.onprogress = (e) => {
    if (e.lengthComputable) {
      const percent = (e.loaded / e.total) * 100;
      updateProgress(percent);
    }
  };
  
  xhr.onload = () => {
    if (xhr.status === 200) showSuccess();
  };
  
  xhr.send(formData);
}

四、Fetch API 原理深度解析

4.1 核心设计:基于 Stream 的请求/响应模型

Fetch 的核心是两个构造函数:

  • Request:表示 HTTP 请求
  • Response:表示 HTTP 响应

两者都实现了 Body mixin,包含可读流(ReadableStream):

javascript 复制代码
fetch('/api/data')
  .then(response => {
    console.log(response.body instanceof ReadableStream); // true
    return response.json(); // 内部读取 body 流并解析
  });

💡 关键机制

Fetch 将响应体视为流(Stream) ,支持边下载边处理,适合大文件或实时数据。

4.2 执行流程(浏览器内部)

以 Chromium 为例:

  1. 调用 fetch(url) → 创建 Request 对象
  2. 浏览器主线程 → 网络服务线程(Network Service)
  3. 网络线程发起 HTTP 请求(复用连接池、DNS 缓存等)
  4. 收到响应头 → 立即 resolve Promise(返回 Response 对象)
  5. 响应体通过 ReadableStream 逐步传输到 JS 主线程
  6. 调用 .json() / .text() 等方法 → 消费流并解析

4.3 与 XHR 的关键差异

特性 XHR Fetch
错误处理 网络错误 → onerror;HTTP 错误(404/500)→ onload 仅网络错误 reject ;HTTP 错误仍 resolve(需手动检查 response.ok
Cookie 发送 同域自动发送 需显式设置 credentials: 'same-origin'
取消请求 xhr.abort() AbortController
上传进度 原生 upload.onprogress 不支持 (需自定义 ReadableStream,复杂)
超时控制 xhr.timeout 需配合 AbortController + setTimeout

错误处理对比示例:

javascript 复制代码
// Fetch:HTTP 404 仍 resolve
fetch('/not-found')
  .then(res => {
    if (!res.ok) { // 必须手动检查
      throw new Error(`HTTP ${res.status}`);
    }
  })
  .catch(err => {
    // 只有网络断开才会进入这里
  });

4.4 Fetch 的局限性与解决方案

问题 1:无法监控下载进度

解决方案:手动读取流并计算进度:

ini 复制代码
const response = await fetch('/large-file');
const contentLength = +response.headers.get('Content-Length');
let loaded = 0;

const reader = response.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  loaded += value.length;
  const progress = (loaded / contentLength) * 100;
  updateProgress(progress);
}

问题 2:无内置超时

解决方案 :结合 AbortController

scss 复制代码
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

fetch('/api/data', { signal: controller.signal })
  .finally(() => clearTimeout(timeoutId));

五、Vue 3 中的网络通信实践

虽然 Vue 本身不强制使用特定 HTTP 客户端,但其组合式 API 与现代请求库天然契合。

5.1 使用 Axios(推荐用于复杂项目)

ini 复制代码
// composables/useApi.js
import axios from 'axios';

const api = axios.create({
  baseURL: '/api',
  timeout: 10000,
  withCredentials: true
});

// 请求拦截器
api.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// 响应拦截器
api.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response?.status === 401) {
      // 处理未授权
      router.push('/login');
    }
    return Promise.reject(error);
  }
);

export default api;
xml 复制代码
<!-- 在组件中使用 -->
<script setup>
import { ref } from 'vue';
import api from '@/composables/useApi';

const users = ref([]);
const loading = ref(false);

const fetchUsers = async () => {
  loading.value = true;
  try {
    users.value = await api.get('/users');
  } finally {
    loading.value = false;
  }
};

fetchUsers();
</script>

5.2 使用 Fetch(轻量级项目)

javascript 复制代码
// utils/request.js
async function request(url, options = {}) {
  const config = {
    credentials: 'include',
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...options.headers
    }
  };

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 10000);
  
  try {
    const response = await fetch(url, {
      ...config,
      signal: controller.signal
    });
    
    clearTimeout(timeoutId);
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    clearTimeout(timeoutId);
    throw error;
  }
}

export { request };

5.3 封装为 Composable(最佳实践)

ini 复制代码
// composables/useFetch.js
import { ref } from 'vue';

export function useFetch(url) {
  const data = ref(null);
  const loading = ref(false);
  const error = ref(null);

  const execute = async () => {
    loading.value = true;
    error.value = null;
    
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error(res.statusText);
      data.value = await res.json();
    } catch (err) {
      error.value = err;
    } finally {
      loading.value = false;
    }
  };

  return { data, loading, error, execute };
}
xml 复制代码
<script setup>
import { useFetch } from '@/composables/useFetch';

const { data: users, loading, execute } = useFetch('/api/users');
execute();
</script>

六、如何选择?------ 使用场景建议

场景 推荐方案 理由
新项目(现代浏览器) fetch() + 工具函数封装 原生支持,无依赖,符合标准
需要上传/下载进度 XMLHttpRequest 或 Axios 原生支持 onprogress,简单可靠
复杂拦截、转换、兼容 Node.js Axios 功能全面,生态成熟
维护旧项目(IE11+) XMLHttpRequest 或 Axios(带 polyfill) 最大兼容性
轻量级应用,避免打包体积 fetch() 无需引入第三方库
Vue 3 项目 Axios(复杂)Fetch + Composable(简单) 与组合式 API 完美契合

📌 现代最佳实践

  • 优先使用 fetch() 或 Axios
  • 将网络逻辑封装为 Composable,实现逻辑复用
  • 避免直接使用裸 XHR(除非特殊需求)

七、安全与性能注意事项

7.1 安全

  • XSS 防护 :永远不要将响应直接插入 innerHTML
  • CSRF 防护:使用 anti-CSRF token,重要操作用非 GET 方法
  • CORS 策略 :服务器严格限制 Access-Control-Allow-Origin
  • 敏感数据:使用 HTTPS,避免客户端存储密码/token

7.2 性能

  • 缓存策略 :合理设置 Cache-Control
  • 请求合并:避免频繁小请求
  • 懒加载:非关键数据延迟请求
  • 取消冗余请求:组件销毁时中止未完成的请求
javascript 复制代码
// Vue 3 中取消请求
import { onUnmounted } from 'vue';

export function useFetch(url) {
  const controller = new AbortController();
  
  onUnmounted(() => {
    controller.abort(); // 组件卸载时取消请求
  });
  
  const execute = () => {
    return fetch(url, { signal: controller.signal });
  };
  
  return { execute };
}

结语

理解 XHR、AJAX、Axios 与 Fetch 的关系,本质上是理解 Web 异步通信技术的演进史:

  • AJAX 提出了"异步更新"的思想
  • XHR 提供了首个标准化实现
  • Axios 在 XHR 基础上构建了开发者友好的抽象
  • Fetch 则代表了浏览器厂商对下一代网络 API 的重新设计

作为开发者,我们不必拘泥于某一种工具,而应根据项目需求、浏览器支持和功能复杂度做出合理选择。但无论使用哪种方式,其背后的核心原理------HTTP 协议、CORS 安全模型、异步编程范式------始终不变。

在 Vue 3 的组合式 API 时代,将网络逻辑封装为可复用的 Composable,不仅能提升代码可维护性,更能充分发挥现代 JavaScript 的表达力。掌握这些底层逻辑,才能在技术变迁中游刃有余,构建出高性能、高安全性的现代 Web 应用。

相关推荐
yuanyxh1 天前
Mac 软件推荐
前端·javascript·程序员
万少1 天前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
某人辛木1 天前
Web自动化测试
前端·python·pycharm·pytest
Kagol1 天前
Superpowers GSD gstack AgentSkills深度测评
前端·人工智能
excel1 天前
JavaScript 字符串与模板字面量:从表象到本质理解
前端
京东云开发者1 天前
当AI成为导演-如何用AI创作动漫短剧
前端
李白的天不白1 天前
使用 SmartAdmin 进行前后端开发
java·前端
乘风gg1 天前
🤡PUA AI Coding 工具 的 10 条终极语录
前端·ai编程·claude
学Linux的语莫1 天前
Vue 3 入门教程
前端·javascript·vue.js
怕浪猫1 天前
第一章、Chrome DevTools Protocol (CDP) 详解
前端·javascript·chrome