XMLHttpRequest(XHR)对象 —— 现代前端的「老将」与「活化石」
虽然现在大家都用 fetch(),但 XMLHttpRequest 依然是前端面试、底层库(Axios、jQuery)、老项目、企业系统中最常见、最重要的 AJAX 技术。很多高级特性(上传进度、超时控制、Abort、同步请求等)fetch 至今仍需 polyfill,而 XHR 原生就支持。
1. 基本信息一览(2025 年最新状态)
| 项目 | 值 / 说明 |
|---|---|
| 构造函数 | new XMLHttpRequest() |
| 全局可用性 | 所有浏览器(包括 IE11+) + Node.js(需 polyfill) |
| 是否过时 | 不推荐新建(MDN 已标记为 Legacy),但仍被广泛使用 |
| 替代品 | fetch()(推荐) |
| 唯一优势(至今不可替代) | 上传/下载进度、AbortController 兼容性、同步请求、FormData 上传进度等 |
2. 完整生命周期与事件(必须背下来的 6 个事件)
const xhr = new XMLHttpRequest();
// 1. 关键事件(按顺序触发)
xhr.onloadstart = () => console.log("开始请求");
xhr.onprogress = (e) => {
if (e.lengthComputable) {
console.log(`已接收 ${e.loaded} / ${e.total} 字节`);
}
};
xhr.onload = () => console.log("请求完成(成功或失败)");
xhr.onloadend = () => console.log("请求彻底结束");
xhr.onerror = () => console.log("网络错误");
xhr.ontimeout = () => console.log("超时");
xhr.onabort = () => console.log("手动中止");
// 2. 就绪状态变化(经典)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) { // DONE
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
console.log("成功", xhr.responseText);
}
}
console.log("readyState:", xhr.readyState);
// 0 UNSENT → 1 OPENED → 2 HEADERS_RECEIVED → 3 LOADING → 4 DONE
};
3. 核心属性与方法(高频面试点)
| 类型 | 名称 | 说明 |
|---|---|---|
| 方法 | open(method, url, async?, user?, password?) | 初始化请求,async 默认 true |
send(body?) | 发送请求,body 可以是 string、Document、Blob、FormData 等 | |
setRequestHeader(name, value) | 设置请求头(必须在 open() 之后、send() 之前) | |
abort() | 中止请求,触发 onabort | |
getResponseHeader(name) | 获取单个响应头 | |
getAllResponseHeaders() | 获取所有响应头(字符串,\r\n 分隔) | |
| 属性 | readyState | 0–4 五个状态 |
status / statusText | HTTP 状态码和文字 | |
response | 响应体(根据 responseType 自动解析) | |
responseText | 永远是字符串(即使出错) | |
responseXML | 如果是 XML 且响应头正确,会自动解析为 Document | |
responseURL | 最终重定向后的 URL(非常有用) | |
timeout | 超时毫秒数(0 表示永不超时) | |
withCredentials | 是否发送跨域 cookie(CORS 需要) | |
upload | 上传专用的 XHRUpload 对象,可监听上传进度! | |
responseType | ” |
4. 经典完整示例(包含所有实战技巧)
function ajax(options) {
const xhr = new XMLHttpRequest();
// 1. 基础配置
xhr.open(options.method || 'GET', options.url, true);
// 2. 超时与中止
xhr.timeout = options.timeout || 10000;
xhr.ontimeout = () => options.error?.('请求超时');
const controller = new AbortController(); // 可外部 abort
options.signal?.addEventListener('abort', () => xhr.abort());
// 3. 上传进度(FormData 上传文件必备)
if (xhr.upload && options.onProgress) {
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
options.onProgress(e.loaded / e.total * 100);
}
};
}
// 4. 响应类型
xhr.responseType = options.responseType || 'json';
// 5. 请求头
if (options.headers) {
for (const [k, v] of Object.entries(options.headers)) {
xhr.setRequestHeader(k, v);
}
}
// 6. 跨域带 cookie
if (options.withCredentials) xhr.withCredentials = true;
// 7. 成功回调
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
options.success?.(xhr.response, xhr);
} else {
options.error?.(new Error(xhr.statusText || 'Request failed'));
}
};
xhr.onerror = () => options.error?.(new Error('Network Error'));
// 8. 发送
xhr.send(options.data || null);
// 返回可中止的对象
return { abort: () => xhr.abort() };
}
5. 上传文件 + 进度条(唯一 fetch 至今仍麻烦的场景)
const fileInput = document.querySelector('input[type=file]');
const progress = document.querySelector('progress');
fileInput.onchange = () => {
const file = fileInput.files[0];
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
progress.value = (e.loaded / e.total) * 100;
}
};
xhr.onload = () => alert('上传成功!');
xhr.onerror = () => alert('上传失败');
const form = new FormData();
form.append('avatar', file);
xhr.send(form);
};
6. XHR vs fetch 终极对比(2025 年版)
| 特性 | XMLHttpRequest | fetch() | 胜者 |
|---|---|---|---|
| 上传进度 | 原生支持(xhr.upload) | 需 ReadableStream 手动实现 | XHR |
| 下载进度 | 原生 onprogress | 需手动解析 body stream | XHR |
| 超时控制 | 原生 timeout | 需 AbortController + setTimeout | XHR |
| 中止请求 | abort() | AbortController | 平手 |
| 同步请求 | 支持(不推荐) | 完全不支持 | XHR |
| 自动解析 JSON | 需手动 JSON.parse | response.json() | fetch |
| 流式处理 | 不支持 | 原生支持 | fetch |
| 跨域带 cookie | withCredentials | credentials: ‘include’ | 平手 |
| 兼容性 | IE11+ | IE 全灭,现代浏览器全支持 | XHR(老项目) |
| 代码简洁性 | 复杂 | 极简 | fetch |
7. 总结:什么时候还得用 XHR?
| 场景 | 必须用 XHR? |
|---|---|
| 需要显示上传进度条 | Yes |
| 大文件分片上传 | Yes |
| 老项目维护(jQuery/AngularJS) | Yes |
| 需要同步请求(极少数场景) | Yes |
| 企业内部系统(IE11 兼容) | Yes |
| 日常 CRUD、JSON 接口 | 推荐 fetch |
一句话定论:
fetch 是未来,XHR 是现在和过去。
学新项目用 fetch,面试 + 维护老项目 + 实现上传进度条 → 必须精通 XMLHttpRequest!
记住这张图,你就永远不会被 XHR 面试题难倒:
open() → setRequestHeader() → send() → onprogress → onload/onerror/ontimeout → abort()