本文详解使用原生 JavaScript 的 XMLHttpRequest 对象实现 Ajax 异步请求的完整步骤,涵盖从对象创建、open 初始化、状态监听、send 发送到 GET/POST 传参及异常处理的全部实操细节。
JavaScript原生XMLHttpRequest实现Ajax请求完整步骤
时间:2026-11-02
浏览:434
文章目录
XMLHttpRequest(简称 XHR)是理解浏览器网络通信底层原理的必经之路。本文将严格遵循规范,通过清晰的 4 个关键步骤,演示如何用纯 JavaScript 完成一次可靠的 Ajax 请求,并说明参数配置与状态捕获的具体逻辑。
一、原生 XMLHttpRequest 发起请求的 4 个核心步骤
使用原生 JavaScript 发起一个标准的 Ajax 请求,本质上由四个连续的代码阶段组成:实例化对象、配置请求参数、挂载事件监听器、发送请求数据。
步骤 1:创建 XMLHttpRequest 实例对象
在标准浏览器中,使用全局构造函数 XMLHttpRequest 生成一个实例:
const xhr = new XMLHttpRequest();
此时,浏览器在内存中分配了通信控制结构,此时 xhr.readyState 的初始值为 0(表示 UNSENT,未初始化)。
步骤 2:调用 open() 方法配置请求参数
open() 方法用于初始化请求,它接受三个常用参数:
xhr.open(method, url, async);
- method(请求方法):通常为
"GET"或"POST",也可以是"PUT"、"DELETE"等。不区分大小写,但规范建议大写。 - url(请求目标地址):目标服务器资源的相对路径或绝对 URL。
- async(是否异步):布尔值,必须显式设置为
true或使用默认值true。如果设为false,主线程将被阻塞,导致页面挂起直至请求完成,现代浏览器已在主线程废弃同步请求。
执行 open() 之后,xhr.readyState 会变为 1(OPENED)。此时网络连接尚未建立。
步骤 3:绑定事件监听器处理响应
由于网络请求是异步的,JavaScript 不会等待网络数据返回,而是通过事件机制在数据就绪时唤醒回调函数。
最经典且兼容性最好的监听方式是挂载 onreadystatechange 事件,检查 readyState 与 status:
xhr.onreadystatechange = function () {
// 1. 判断通信是否全部完成 (readyState === 4)
if (xhr.readyState === 4) {
// 2. 判断 HTTP 响应状态码是否在成功区间 (200~299 或 304 缓存)
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log("请求成功,响应数据:", xhr.responseText);
} else {
console.error("请求失败,HTTP 状态码:", xhr.status);
}
}
};
xhr.onload = function() { ... }。它只在请求成功完成时(等价于 readyState === 4)触发。但 onreadystatechange 依然是理解整个请求流转全生命周期的基石。
步骤 4:调用 send() 方法发送请求
最后一步是调用 send() 方法,真正将数据包推入网络发送管道:
// 如果是 GET 请求,无需发送请求体,参数传 null 或留空
xhr.send(null);
// 如果是 POST 请求,将格式化好的字符串或 FormData 作为参数传入
// xhr.send(JSON.stringify({ id: 1001 }));
一旦调用 send(),浏览器便在后台启动网络线程开始三次握手与数据传输。
二、核心机制:readyState 状态迁移与生命周期事件
要精确控制网络行为并排除偶发异常,必须深入理解 XHR 内部的 readyState 状态机。在一次完整的网络通信过程中,readyState 总是按 0 -> 1 -> 2 -> 3 -> 4 递增。
| 状态值 (readyState) | 常量名称 | 说明 | 读取属性限制 |
|---|---|---|---|
0 |
UNSENT |
XHR 代理对象已被创建,但尚未调用 open()。 |
无法获取 status 或 response。 |
1 |
OPENED |
已调用 open() 方法,连接参数已确立。 |
可调用 setRequestHeader()。 |
2 |
HEADERS_RECEIVED |
send() 已调用,且已接收到所有的 HTTP 响应头和响应状态码。 |
可读取 xhr.status 与响应头。 |
3 |
LOADING |
正在接收响应主体,responseText 中可能包含部分已下载内容。 |
数据不完整,不建议在此阶段读取业务数据。 |
4 |
DONE |
整个通信过程彻底完成,所有数据包传输完毕或发生故障中断。 | 数据可用,可完整读取 xhr.responseText。 |
三、GET 与 POST 请求的实现与传参对比
在实际业务中,获取数据通常使用 GET 请求,提交数据则使用 POST 请求。两者的参数传递位置和标头配置具有显著差异:
1. 完整 GET 请求代码示例
GET 请求通常通过 URL 传递查询字符串。如果包含中文或特殊符号,必须通过 encodeURIComponent() 进行编码以防乱码:
// 1. 实例化
const xhr = new XMLHttpRequest();
// 2. 拼接 URL 查询参数
const queryParam = encodeURIComponent("前端开发");
const url = `/api/search?keyword=${queryParam}×tamp=${Date.now()}`;
// 3. 配置请求
xhr.open("GET", url, true);
// 4. 设置期待的响应格式(现代浏览器推荐)
xhr.responseType = "json";
// 5. 监听通信状态
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 当设置了 responseType = "json" 时,直接读取 xhr.response 即可得到解析好的对象
console.log("获取成功:", xhr.response);
} else {
console.error("请求失败,HTTP 状态:", xhr.status);
}
}
};
// 6. 发出请求
xhr.send(null);
2. 完整 POST 请求代码示例 (发送 JSON 数据)
发送 POST 请求时,必须在 open() 之后、send() 之前设置 Content-Type 标头,否则后端接口无法正确辨识数据反序列化方式:
// 1. 实例化
const xhr = new XMLHttpRequest();
// 2. 初始化连接
xhr.open("POST", "/api/article/create", true);
// 3. 【关键步骤】必须在 open() 之后设置 Content-Type 标头
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
// 4. 配置超时时间与超时处理
xhr.timeout = 8000; // 8 秒超时
xhr.ontimeout = function () {
console.error("网络请求超时,请检查您的网络连接");
};
// 5. 绑定响应处理函数
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const result = JSON.parse(xhr.responseText);
console.log("提交成功,服务端返回:", result);
} catch (e) {
console.error("响应数据不是合法 JSON:", xhr.responseText);
}
} else {
console.error("提交失败,状态码:", xhr.status);
}
}
};
// 6. 将 JavaScript 对象序列化为 JSON 字符串后通过 send() 传输
const payload = {
title: "原生 XMLHttpRequest 实战",
status: "published"
};
xhr.send(JSON.stringify(payload));
四、异常排查决策链路与核心避坑指南
在原生开发中,网络请求可能会遭遇 DNS 解析错误、断网、跨域 CORS 拦截或请求超时等问题。构建健壮的 Ajax 调用必须覆盖完整的异常分支。
核心注意事项与避坑指南
-
setRequestHeader 的调用时机:
setRequestHeader()必须严格在open()之后且在send()之前调用。如果在open()之前调用,浏览器将直接抛出DOMException: InvalidStateError。 -
跨域资源共享(CORS):
当请求不同源(协议、域名或端口任一不同)的接口时,浏览器会触发同源策略校验。如果服务端没有返回包含
Access-Control-Allow-Origin的响应头,浏览器会强行拦截响应,触发onerror事件且xhr.status会表现为0。如需在跨域请求中携带 Cookie,需在调用send()之前设置:xhr.withCredentials = true; -
状态码为 0 的场景:
如果
xhr.status === 0,通常意味着网络请求未正常完成。排查顺序应为:本地网络是否连通 > 跨域 CORS 是否被浏览器拦截 > 目标地址是否发生混杂内容拦截(HTTPS 页面中请求 HTTP 资源)。 -
手动终止请求:
在用户频繁切换 Tab 或发起重复查询时,可以调用
xhr.abort()主动切断通信。调用后readyState将被重置为0,并触发abort事件,能有效节省带宽并避免竞态问题。
五、工程化实践:封装通用 Promise 化 Ajax 函数
在现代项目中,直接裸写回调形式的 XHR 容易导致回调地狱。我们可以运用 ES6 Promise 将上述四个步骤包装成一个统一、简洁的异步工具函数:
/**
* 基于原生 XMLHttpRequest 封装的通用 Ajax 函数
* @param {Object} options 请求配置参数
* @param {string} options.url 请求地址
* @param {string} [options.method='GET'] 请求类型
* @param {Object} [options.headers={}] 自定义请求头
* @param {Object|string} [options.data=null] 请求体数据
* @param {number} [options.timeout=10000] 超时时间(毫秒)
* @returns {Promise<any>}
*/
function ajax({
url,
method = "GET",
headers = {},
data = null,
timeout = 10000
}) {
return new Promise((resolve, reject) => {
// 步骤 1:创建 XHR 对象
const xhr = new XMLHttpRequest();
// 步骤 2:初始化通道
xhr.open(method.toUpperCase(), url, true);
xhr.timeout = timeout;
// 设置自定义请求头(必须在 open 之后)
for (const [key, value] of Object.entries(headers)) {
xhr.setRequestHeader(key, value);
}
// 步骤 3:绑定事件监听器
xhr.onload = function () {
// 校验 HTTP 响应状态码
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
let responseData = xhr.responseText;
try {
responseData = JSON.parse(xhr.responseText);
} catch (err) {
// 非 JSON 格式则保持原样字符串返回
}
resolve({
status: xhr.status,
data: responseData,
headers: xhr.getAllResponseHeaders()
});
} else {
reject(new Error(`HTTP 请求失败,状态码: ${xhr.status}`));
}
};
xhr.onerror = function () {
reject(new Error("网络连接异常或触发 CORS 跨域拦截"));
};
xhr.ontimeout = function () {
reject(new Error(`请求超时,超过了设定的 ${timeout}ms`));
};
// 步骤 4:处理载荷并发送
let payload = null;
if (data) {
if (typeof data === "object" && !(data instanceof FormData)) {
if (!headers["Content-Type"]) {
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
}
payload = JSON.stringify(data);
} else {
payload = data;
}
}
xhr.send(payload);
});
}
// 实际调用示例:
// ajax({
// url: "https://api.example.com/v1/profile",
// method: "GET"
// }).then(res => {
// console.log("用户信息:", res.data);
// }).catch(err => {
// console.error("加载失败:", err.message);
// });
通过以上封装,我们既保留了原生 XMLHttpRequest 清晰透明的底层网络控制力,又能无缝融入现代前端的 async/await 异步编程范式中。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。