原生XMLHttpRequest + axios + fetch

原生XMLHttpRequest + axios + fetch

📖 前言

AJAX(Asynchronous JavaScript and XML)是前端开发中最重要的技术之一,它允许网页在不重新加载整个页面的情况下与服务器进行异步通信,实现局部刷新。从最早的 XMLHttpRequest 到如今的 axiosfetch,AJAX 的实现方式不断演进,但其核心思想始终如一------异步数据交互

📋 目录

  • 一、什么是AJAX
  • [二、原生AJAX ------ XMLHttpRequest](#二、原生AJAX —— XMLHttpRequest)
    • [2.1 GET请求](#2.1 GET请求)
    • [2.2 POST请求](#2.2 POST请求)
    • [2.3 响应JSON数据](#2.3 响应JSON数据)
  • 三、AJAX常见问题与解决方案
    • [3.1 IE缓存问题](#3.1 IE缓存问题)
    • [3.2 超时与网络异常](#3.2 超时与网络异常)
    • [3.3 取消请求](#3.3 取消请求)
    • [3.4 重复请求防抖](#3.4 重复请求防抖)
  • 四、现代AJAX方案
    • [4.1 axios](#4.1 axios)
    • [4.2 Fetch API](#4.2 Fetch API)
  • [五、跨域问题 ------ CORS](#五、跨域问题 —— CORS)
  • 六、总结

一、什么是AJAX

AJAX 不是一门新的编程语言,而是一种创建交互式网页应用的网页开发技术。它的核心在于:

  • 异步(Asynchronous):请求发出后,JS 不会阻塞等待响应,而是继续执行后续代码
  • JavaScript:通过 JS 操作 DOM 来更新页面内容
  • XML:早期数据交换格式,现在更多使用 JSON

AJAX 的优势:

  • 无需刷新页面即可更新数据,用户体验好
  • 减轻服务器压力,只请求需要的数据
  • 可以实现丰富的交互效果

二、原生AJAX ------ XMLHttpRequest

XMLHttpRequest(简称 XHR)是浏览器提供的内置对象,用于与服务器交互数据。它是 AJAX 的底层实现。

2.1 GET请求

GET 是最常用的请求方式,用于从服务器获取数据,参数通过 URL 传递。

html 复制代码
<button>发送GET请求</button>
<div id="result"></div>

<script>
  // 获取button元素
  const btn = document.querySelector('button')
  const result = document.querySelector('#result')

  // 给button添加点击事件
  btn.addEventListener('click', () => {
    // 1. 创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest()

    // 2. 初始化:设置请求方法和url
    //    参数通过 ? 拼接在URL后面,多个参数用 & 分隔
    xhr.open('GET', 'http://127.0.0.1:8000/server?a=100&b=200')

    // 3. 发送请求
    xhr.send()

    // 4. 监听状态变化事件
    xhr.onreadystatechange = () => {
      // readyState === 4 表示服务端返回了所有结果
      if (xhr.readyState === 4) {
        // 判断响应状态码:2xx 表示成功
        if (xhr.status >= 200 && xhr.status < 300) {
          // 5. 处理响应数据
          result.innerHTML = xhr.responseText
          console.log('状态码:', xhr.status)
          console.log('响应体:', xhr.responseText)
        }
      }
    }
  })
</script>

核心要点:

属性/方法 说明
new XMLHttpRequest() 创建 XHR 对象
xhr.open(method, url) 初始化请求,设置方法和地址
xhr.send() 发送请求
xhr.onreadystatechange 状态变化回调
xhr.readyState 请求状态(0-4)
xhr.status HTTP 状态码
xhr.responseText 响应文本

readyState 状态说明:

  • 0 --- 未初始化,尚未调用 open()
  • 1 --- 启动,已调用 open(),尚未调用 send()
  • 2 --- 发送,已调用 send(),尚未收到响应
  • 3 --- 接收,正在解析响应数据
  • 4 --- 完成,响应解析完成,可以使用数据了

2.2 POST请求

POST 请求通常用于向服务器提交数据,数据放在请求体中传递。

html 复制代码
<div id="result"></div>

<script>
  const result = document.querySelector('#result')

  // 鼠标移入div时发送POST请求
  result.addEventListener('mouseenter', () => {
    // 1. 创建对象
    const xhr = new XMLHttpRequest()

    // 2. 初始化
    xhr.open('POST', 'http://127.0.0.1:8000/server')

    // 设置请求头(Content-Type 指定请求体格式)
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')

    // 3. 发送请求,参数放在send()中
    xhr.send('a=100&b=200')

    // 4. 监听状态变化
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          result.innerHTML = xhr.responseText
        }
      }
    }
  })
</script>

POST vs GET 的区别:

对比项 GET POST
参数位置 URL 拼接 请求体(body)
数据大小 受 URL 长度限制 理论上无限制
安全性 参数暴露在URL中 相对安全
缓存 会被浏览器缓存 默认不缓存
用途 获取数据 提交数据

常见 Content-Type:

  • application/x-www-form-urlencoded --- 表单格式,如 a=1&b=2
  • application/json --- JSON 格式,如 {"name":"张三"}
  • multipart/form-data --- 文件上传时使用

2.3 响应JSON数据

实际开发中,服务器返回的大多是 JSON 格式数据,需要手动解析。

html 复制代码
<div id="result"></div>

<script>
  const result = document.querySelector('#result')

  // 按下任意键发送请求
  window.addEventListener('keydown', () => {
    const xhr = new XMLHttpRequest()

    // 方式一:手动设置响应类型(推荐)
    // xhr.responseType = 'json'

    xhr.open('GET', 'http://127.0.0.1:8000/json-server')
    xhr.send()

    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          // 方式二:手动解析JSON字符串
          let data = JSON.parse(xhr.responseText)
          result.innerHTML = data.name
          console.log(data) // { name: '张三', age: 18 }

          // 如果设置了 responseType = 'json'
          // 则 xhr.response 直接就是对象,无需 parse
        }
      }
    }
  })
</script>

两种解析 JSON 的方式:

  1. 手动解析JSON.parse(xhr.responseText) --- 兼容性好
  2. 自动解析 :设置 xhr.responseType = 'json' --- 更简洁,xhr.response 直接是对象

三、AJAX常见问题与解决方案

3.1 IE缓存问题

问题描述: 在 IE 浏览器中,GET 请求会被缓存。如果服务器数据更新了,但 URL 没变,IE 仍然会返回缓存中的旧数据。

解决方案: 在 URL 后拼接一个时间戳或随机数,让每次请求的 URL 都不一样。

html 复制代码
<button>发送请求</button>
<div id="result"></div>

<script>
  const btn = document.querySelector('button')
  const result = document.querySelector('#result')

  btn.addEventListener('click', () => {
    const xhr = new XMLHttpRequest()

    // 解决方案:URL后拼接时间戳
    // 每次请求URL都不同,浏览器就不会走缓存
    xhr.open('GET', 'http://127.0.0.1:8000/ie?t=' + Date.now())

    xhr.send()
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
        result.innerHTML = xhr.responseText
      }
    }
  })
</script>

原理: 浏览器的缓存是基于 URL 的。只要 URL 变了,浏览器就认为是新请求,不会走缓存。Date.now() 每次返回不同的时间戳,保证 URL 唯一。

💡 现代浏览器(Chrome/Firefox/Edge) 对缓存策略做了优化,这个问题主要出现在老版本 IE 中,但了解原理有助于理解浏览器缓存机制。


3.2 超时与网络异常

问题描述: 网络不好或服务器响应慢时,请求可能长时间无响应,用户体验很差。需要设置超时时间和异常处理。

html 复制代码
<button>发送请求</button>
<div id="result"></div>

<script>
  const btn = document.querySelector('button')
  const result = document.querySelector('#result')

  btn.addEventListener('click', () => {
    const xhr = new XMLHttpRequest()

    // 设置超时时间(单位:毫秒)
    // 超过2秒未响应则自动取消请求
    xhr.timeout = 2000

    // 超时回调
    xhr.ontimeout = () => {
      result.innerHTML = '⏰ 请求超时,请稍后重试'
      result.style.color = 'orange'
    }

    // 网络异常回调(断网等情况)
    xhr.onerror = () => {
      result.innerHTML = '📡 网络异常,请检查网络连接'
      result.style.color = 'red'
    }

    xhr.open('GET', 'http://127.0.0.1:8000/delay')
    xhr.send()

    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
        result.innerHTML = xhr.responseText
        result.style.color = 'green'
      }
    }
  })
</script>

关键属性:

属性 说明
xhr.timeout 超时时间,单位毫秒
xhr.ontimeout 超时触发的回调函数
xhr.onerror 网络异常触发的回调函数

⚠️ 注意:timeout 必须在 open() 之后、send() 之前设置才有效。


3.3 取消请求

问题描述: 用户频繁操作(如快速点击搜索按钮)时,可能发出多个请求,导致结果错乱。需要能手动取消请求。

html 复制代码
<button id="sendBtn">发送请求</button>
<button id="cancelBtn">取消请求</button>

<script>
  const sendBtn = document.querySelector('#sendBtn')
  const cancelBtn = document.querySelector('#cancelBtn')

  // 将xhr声明在外层,方便两个按钮共享
  let xhr = null

  sendBtn.addEventListener('click', () => {
    xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://127.0.0.1:8000/cancel')
    xhr.send()
    console.log('请求已发送')
  })

  cancelBtn.addEventListener('click', () => {
    // 调用 abort() 取消请求
    xhr.abort()
    console.log('请求已取消')
  })
</script>

核心方法:xhr.abort()

  • 调用后立即终止当前请求
  • 触发 onabort 事件(可监听)
  • 取消后 readyState 变为 0status 变为 0

3.4 重复请求防抖

问题描述: 用户快速连续点击按钮,会发出大量重复请求,浪费服务器资源,还可能导致后发的请求先返回,结果覆盖问题。

解决方案: 每次发送新请求前,先取消上一次未完成的请求。

html 复制代码
<button id="sendBtn">发送请求</button>

<script>
  const sendBtn = document.querySelector('#sendBtn')

  // 标识变量:当前是否正在发送请求
  let isSending = false
  let xhr = null

  sendBtn.addEventListener('click', () => {
    // 如果上一个请求还在进行中,先取消它
    if (isSending) {
      xhr.abort()
    }

    xhr = new XMLHttpRequest()
    isSending = true // 标记为正在请求

    xhr.open('GET', 'http://127.0.0.1:8000/cancel')
    xhr.send()

    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        // 请求完成,重置标识
        isSending = false

        if (xhr.status >= 200 && xhr.status < 300) {
          console.log(xhr.responseText)
        }
      }
    }
  })
</script>

防抖思路:

  1. 用一个变量 isSending 记录当前是否有请求在进行
  2. 新请求发出前,如果有未完成的请求,先 abort() 取消
  3. 请求完成后重置标识变量

这样无论用户点击多快,始终只有最新的一次请求有效。


四、现代AJAX方案

原生 XMLHttpRequest 写法繁琐,API 设计不够友好。现代开发中,我们通常使用更简洁的方案。

4.1 axios

axios 是目前最流行的 HTTP 客户端库,基于 Promise 封装,支持浏览器和 Node.js。

引入方式:

html 复制代码
<!-- CDN引入 -->
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.4.0/axios.min.js"></script>

基本使用:

html 复制代码
<button>GET请求</button>
<button>POST请求</button>

<script>
  const btns = document.querySelectorAll('button')

  // 配置基础URL(后续请求会自动拼接)
  axios.defaults.baseURL = 'http://127.0.0.1:8000'

  // GET请求
  btns[0].addEventListener('click', () => {
    axios.get('/axios', {
      // URL参数(自动拼接到URL后)
      params: {
        a: 100,
        b: 200
      },
      // 请求头
      // headers: { name: 'axios' }
    }).then(res => {
      console.log('响应数据:', res.data)
      console.log('状态码:', res.status)
      console.log('响应头:', res.headers)
    }).catch(err => {
      console.error('请求失败:', err)
    })
  })

  // POST请求
  btns[1].addEventListener('click', () => {
    axios.post('/axios', {
      // 请求体数据(自动转为JSON)
      username: 'admin',
      password: '123456'
    }).then(res => {
      console.log(res.data)
    })
  })
</script>

axios 的优势:

  • ✅ 基于 Promise,支持 async/await
  • ✅ 自动转换 JSON 数据
  • ✅ 支持请求/响应拦截器
  • ✅ 支持取消请求
  • ✅ 客户端支持防止 CSRF
  • ✅ 支持并发请求(axios.all

常用 API 对比:

功能 原生XHR axios
创建请求 new XMLHttpRequest() axios.get/post/...
传参 手动拼接URL / send() params / data 对象
解析JSON JSON.parse() 自动解析,res.data 直接用
错误处理 判断 status .catch() 统一捕获
取消请求 xhr.abort() AbortController / CancelToken

4.2 Fetch API

Fetch 是 ES6 新增的原生 API,用于替代 XMLHttpRequest,同样基于 Promise 设计。

html 复制代码
<button>发送AJAX请求</button>

<script>
  const btn = document.querySelector('button')

  btn.addEventListener('click', () => {
    fetch('http://127.0.0.1:8000/fetch', {
      // 请求方法
      method: 'POST',
      // 请求头
      // headers: { 'Content-Type': 'application/json' },
      // 请求体
      body: 'username=admin&password=123456'
    })
    .then(res => {
      // 第一步:获取响应对象,需要调用方法解析
      // res.text()  --- 解析为文本
      // res.json()  --- 解析为JSON对象
      return res.text()
      // return res.json()
    })
    .then(data => {
      // 第二步:拿到解析后的数据
      console.log(data)
    })
    .catch(err => {
      console.error('请求失败:', err)
    })
  })
</script>

Fetch 的特点:

  • ✅ 原生 API,无需引入第三方库
  • ✅ 基于 Promise,写法简洁
  • ✅ 模块化设计,Response/Request/Headers 对象分离
  • ❌ 不支持取消请求(需配合 AbortController)
  • ❌ 不支持超时设置(需自己封装)
  • ❌ 404/500 等错误状态码不会 reject,需要手动判断

Fetch 与 axios 的对比:

对比项 Fetch axios
本质 浏览器原生API 第三方库(封装XHR)
Promise支持
自动JSON转换 ❌ 需手动调用.json() ✅ 自动转换
错误处理 ❌ 404不reject ✅ 状态码非2xx都reject
取消请求 ❌ 需AbortController ✅ 内置支持
超时设置 ❌ 需自己封装 ✅ timeout配置
拦截器 ❌ 无 ✅ 请求/响应拦截器
兼容性 现代浏览器,IE不支持 好,兼容IE8+

💡 选型建议: 项目中推荐使用 axios,功能更完善,开发效率更高。Fetch 适合简单场景或不想引入第三方库的情况。


五、跨域问题 ------ CORS

什么是跨域? 浏览器的同源策略(Same-Origin Policy)规定:协议、域名、端口三者必须完全相同,否则就是跨域,浏览器会阻止请求。

同源示例:

  • http://example.com:80http://example.com:80 → 同源
  • http://example.comhttps://example.com → 跨域(协议不同)
  • http://a.example.comhttp://b.example.com → 跨域(域名不同)
  • http://example.com:8000http://example.com:3000 → 跨域(端口不同)

CORS 解决方案

CORS(Cross-Origin Resource Sharing)是最常用的跨域解决方案,通过在服务端设置响应头来允许跨域。

服务端设置(Node.js / Express):

javascript 复制代码
const express = require('express')
const app = express()

// 允许所有来源跨域
app.all('/server', (req, res) => {
  // 设置允许跨域的源,* 表示允许所有
  res.setHeader('Access-Control-Allow-Origin', '*')

  // 设置允许的请求方法
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')

  // 设置允许的请求头
  // res.setHeader('Access-Control-Allow-Headers', '*')

  res.send('hello AJAX')
})

app.listen(8000, () => {
  console.log('服务已启动,端口8000')
})

关键响应头说明:

响应头 说明
Access-Control-Allow-Origin 允许跨域的源,* 表示所有
Access-Control-Allow-Methods 允许的请求方法
Access-Control-Allow-Headers 允许的请求头
Access-Control-Max-Age 预检请求缓存时间
Access-Control-Allow-Credentials 是否允许携带Cookie

CORS 的两种请求:

  1. 简单请求:GET/POST/HEAD,且请求头不超出安全字段 → 直接发送
  2. 预检请求:PUT/DELETE 或自定义请求头 → 先发 OPTIONS 预检,服务端确认后再发实际请求

⚠️ 注意:Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true 不能同时使用,需要指定具体的 origin。


六、总结

本文系统梳理了 AJAX 从原生到现代方案的完整知识体系,让我们回顾一下核心要点:

🎯 知识图谱

复制代码
AJAX
├── 原生实现:XMLHttpRequest
│   ├── GET请求(URL传参)
│   ├── POST请求(请求体传参)
│   └── JSON响应(JSON.parse / responseType)
├── 常见问题
│   ├── IE缓存 → 时间戳方案
│   ├── 超时异常 → timeout / onerror
│   ├── 取消请求 → abort()
│   └── 重复请求 → 防抖取消
├── 现代方案
│   ├── axios(推荐,功能完善)
│   └── Fetch API(原生,简洁)
└── 跨域问题
    └── CORS(服务端设置响应头)

💡 实战建议

  1. 日常开发优先用 axios:拦截器、取消请求、自动转换 JSON 等特性大幅提升开发效率
  2. 理解原生 XHR:面试常考,也是理解所有请求库的基础
  3. 重视错误处理:超时、网络异常、状态码判断缺一不可
  4. 防抖/节流:搜索框、按钮点击等场景务必加上,避免无效请求
  5. 跨域理解:CORS 是最主流方案,理解响应头配置和预检请求机制
相关推荐
zhangjin112014 天前
Okhttp系列:POST请求
okhttp
এ慕ོ冬℘゜17 天前
深入理解 JavaScript 事件体系:Window、鼠标与键盘事件详解
开发语言·javascript·okhttp
牢七19 天前
wordpress
okhttp
脉动数据行情22 天前
Java OkHttp 完整封装 GC COMEX 美黄金实时盘口定时拉取工具类
java·okhttp·comex 黄金·外盘贵金属
帅次23 天前
Android 高级工程师面试:网络与数据层 近1年高频追问 18 题
android·网络·okhttp·面试·retrofit·gilde
Java面试题总结24 天前
fastadmin 新手部分功能点
okhttp
殳翰1 个月前
向客户端提供JSON数据的方式
okhttp·json
CYY951 个月前
OkHttp 和 Retrofit 封装使用
okhttp·retrofit