原生XMLHttpRequest + axios + fetch
📖 前言
AJAX(Asynchronous JavaScript and XML)是前端开发中最重要的技术之一,它允许网页在不重新加载整个页面的情况下与服务器进行异步通信,实现局部刷新。从最早的 XMLHttpRequest 到如今的 axios、fetch,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=2application/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 的方式:
- 手动解析 :
JSON.parse(xhr.responseText)--- 兼容性好 - 自动解析 :设置
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变为0,status变为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>
防抖思路:
- 用一个变量
isSending记录当前是否有请求在进行 - 新请求发出前,如果有未完成的请求,先
abort()取消 - 请求完成后重置标识变量
这样无论用户点击多快,始终只有最新的一次请求有效。
四、现代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:80和http://example.com:80→ 同源http://example.com和https://example.com→ 跨域(协议不同)http://a.example.com和http://b.example.com→ 跨域(域名不同)http://example.com:8000和http://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 的两种请求:
- 简单请求:GET/POST/HEAD,且请求头不超出安全字段 → 直接发送
- 预检请求: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(服务端设置响应头)
💡 实战建议
- 日常开发优先用 axios:拦截器、取消请求、自动转换 JSON 等特性大幅提升开发效率
- 理解原生 XHR:面试常考,也是理解所有请求库的基础
- 重视错误处理:超时、网络异常、状态码判断缺一不可
- 防抖/节流:搜索框、按钮点击等场景务必加上,避免无效请求
- 跨域理解:CORS 是最主流方案,理解响应头配置和预检请求机制