JS中XMLHttpRequest (XHR) 发送HTTP请求的步骤详解
深入讲解JavaScript中使用XMLHttpRequest (XHR) 发送HTTP请求的完整步骤、生命周期状态、GET与POST数据封装差异、异常排错及核心最佳实践。
XMLHttpRequest(简称 XHR)依然是深入理解浏览器异步网络传输、精准控制上传进度与排查底层 HTTP 问题的坚实基石。发送一个标准的 XHR 请求遵循严密的时序逻辑,包含:创建实例、初始化配置、注册监听回调、配置报头、发送数据体五个基本步骤。本文将结合代码实现、状态机流转与结构对比,清晰拆解每一个关键环节。
一、XHR 发送 HTTP 请求的标准五大步骤
为了保证网络连接可靠建立并不丢失服务端推送的事件通知,浏览器对 XMLHttpRequest 的调用顺序有明确要求。下面按照开发中最规范的执行路径逐步展开。
在向服务器发出请求前,首先要在当前执行上下文中实例化一个通信对象:
const xhr = new XMLHttpRequest();
执行该语句后,浏览器在内存中分配与当前请求关联的通道句柄,此时对象的 readyState 状态属性初始为 0(对应常量 XMLHttpRequest.UNSENT),网络通道尚未被激活。
使用 xhr.open() 方法指定本次通信的 HTTP 动词、目标地址以及是否异步:
xhr.open(method, url, async, user, password);
method:请求方法,如'GET'、'POST'、'PUT'、'DELETE'(建议大写)。url:目标请求路径,可为相对路径(如/api/data)或绝对 URL。async:布尔值,是否异步执行。默认值为true。严禁在生产主线程将其设为false(同步会冻结 UI 渲染与交互)。user/password:可选参数,用于 HTTP 基本身份认证。
调用 open() 成功后,readyState 立即跃迁为 1(XMLHttpRequest.OPENED)。此时请求并未向网络发出,仅在内部完成上下文初始化。
虽然技术上在调用 send() 之前绑定事件均可,但最佳实践是紧随 open() 之后立即注册监听函数,以防某些同源缓存响应在触发瞬间被遗漏。
现代浏览器标准推荐直接使用 xhr.onload 与 xhr.onerror,或传统通用的 xhr.onreadystatechange:
// 方式 A:现代化事件监听(推荐)
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('请求成功:', xhr.response);
} else {
console.error('服务端返回异常状态:', xhr.status, xhr.statusText);
}
};
xhr.onerror = function() {
console.error('网络层故障或跨域拦截');
};
// 方式 B:经典状态机监听
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('数据接收完成:', xhr.responseText);
}
}
};
如果请求需要向服务端提交数据(如 JSON、表单数据),或者需要附带鉴权凭据(如 Token),必须使用 setRequestHeader():
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.setRequestHeader('Authorization', 'Bearer eyJhbGciOi...');
setRequestHeader() 必须在 open() 之后、且在 send() 之前调用。若在 open() 之前调用,浏览器会抛出 InvalidStateError 异常;若在 send() 之后调用,报头早已封包发送,配置将无效。
调用 xhr.send() 正式把请求推入浏览器网络栈,底层开始 TCP 握手并传输 HTTP 报文:
// 发送 GET 请求(无需载荷)
xhr.send(null);
// 发送 POST 请求(携带 JSON 字符串或 FormData)
const payload = { username: "developer", role: "admin" };
xhr.send(JSON.stringify(payload));
如果使用异步模式(默认),send() 方法调用后会立即返回,程序继续往下执行,不会发生线程阻塞。当服务端完成响应传输后,先前注册的回调函数便会被事件循环依次唤醒。
二、核心状态机:readyState 与事件生命周期
要写出鲁棒的通信代码,必须准确区分 readyState(XHR 自身通道状态)与 status(HTTP 响应状态码)的区别。
1. readyState 五种常量的含义
| 值 | 常量名称 | 触发时机 | 可用属性与典型操作 |
|---|---|---|---|
0 |
UNSENT |
刚刚执行 new XMLHttpRequest() |
尚未调用 open(),status 和 response 均为空 |
1 |
OPENED |
成功执行 xhr.open() |
可设置请求头 setRequestHeader 及 withCredentials |
2 |
HEADERS_RECEIVED |
已调用 send() 且已收到 HTTP 响应头 |
xhr.status 及 getResponseHeader() 已经可读取 |
3 |
LOADING |
正在下载 HTTP 响应体数据 | responseText 持有已收到的部分分片数据 |
4 |
DONE |
请求全部完成或发生不可逆错误 | 完整响应数据可访问,整个网络事务闭环 |
readyState === 4 只代表底层网络请求完整走完了往返流程,即使服务端返回了 404 Not Found 或 500 Internal Server Error,readyState 依然会变为 4。
因此业务逻辑判定数据是否有效,必须双重确认:xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)。
三、实战范例:GET 与 POST 请求的完整实现
下面分别给出包含参数拼接、异常捕获、响应类型转换的规范代码模板。
1. 完整 GET 请求示例(含 Query 参数编码与 responseType)
/**
* 发送标准 GET 请求
* @param {string} baseUrl - 目标接口基础地址
* @param {object} params - 查询参数键值对
*/
function fetchUserData(baseUrl, params) {
// 1. 拼接并转义 Query 查询字符串
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
// 2. 实例化
const xhr = new XMLHttpRequest();
// 3. 打开连接
xhr.open('GET', fullUrl, true);
// 4. 优化响应解析:指定返回为 JSON 对象,无需手动 JSON.parse
xhr.responseType = 'json';
// 5. 设置超时时间(单位:毫秒)
xhr.timeout = 8000;
// 6. 注册事件监听
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('获取数据成功:', xhr.response);
} else {
console.error('业务失败,状态码:', xhr.status);
}
};
xhr.ontimeout = function() {
console.error('网络请求超时,耗时超过 8000ms');
};
xhr.onerror = function() {
console.error('发生网络底层错误或跨域违规');
};
// 7. 发送请求(GET 传 null)
xhr.send(null);
}
// 调用示例
fetchUserData('https://api.example.com/users', { page: 1, pageSize: 20 });
2. 完整 POST 请求示例(提交 JSON 数据载荷)
/**
* 发送标准 POST 请求提交 JSON 数据
* @param {string} url - 提交地址
* @param {object} data - 负载对象
*/
function submitOrder(url, data) {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
// 关键步骤:设置报文头,声明请求体数据为 JSON 格式
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.setRequestHeader('Accept', 'application/json');
xhr.responseType = 'json';
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('订单提交成功,返回数据:', xhr.response);
} else {
console.error('提交失败:', xhr.status, xhr.statusText);
}
};
xhr.onerror = function() {
console.error('提交发生网络错误');
};
// 将 JavaScript 对象序列化为 JSON 字符串并压入请求体
const bodyPayload = JSON.stringify(data);
xhr.send(bodyPayload);
}
// 调用示例
submitOrder('https://api.example.com/orders', {
goodsId: 10086,
quantity: 2,
payChannel: 'wechat'
});
四、进阶场景与常见避坑指南
1. 跨域资源共享 (CORS) 与凭据携带
当 XHR 访问不同源(协议、域名、端口任一不同)的资源时,浏览器安全机制会触发同源策略检查:
- 基本跨域:服务端必须在响应头中包含
Access-Control-Allow-Origin: *或指定来源站点。否则客户端触发onerror,status为0,在控制台打印 CORS 拦截提示。 - 携带 Cookie 与身份认证:默认情况下,跨域 XHR 不会发送 Cookie。若要携带 Cookie,需在调用
send()之前设置:
xhr.withCredentials = true;
此时服务端响应头必须明确指定具体域名,且附带Access-Control-Allow-Credentials: true,绝不能配置为通配符 *。
2. 文件上传与进度监控 (XHR Level 2)
XHR 相比原始 Fetch 的一大优势在于对上传进度的原生支持。在 xhr.upload 对象上可监听 onprogress:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload', true);
// 监听上传进度
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
console.log(`上传进度: ${percentComplete}%`);
}
};
// 注意:上传 FormData 时严禁手动设置 Content-Type,让浏览器自动补充 boundary 分界字符串!
xhr.send(formData);
3. 请求超时控制与手动中止
在用户快速切换页面、Tab 标签或输入自动补全时,未完成的请求应及时丢弃以节省带宽:
// 随时调用 abort() 中止尚未完成的请求
xhr.abort(); // 此时 readyState 重置为 0,且触发 onabort 回调
五、结语与总结
熟练掌握 XMLHttpRequest 的五大操作步骤(new 实例化 → open 配置 → onload 监听 → setRequestHeader 报头 → send 投递)及其背后的状态机运作机制,不仅是应对老旧项目维护与高精细度网络任务(如带进度的断点续传、底层报文探针)的必备技能,更能帮助前端开发者从根本上透彻理解现代 Fetch、Axios 乃至全栈 HTTP 通信库的演化逻辑。
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
JavaScript新手常犯的编码错误与最佳实践指南
2026-11-04 22:12:38








