JavaScript原生XMLHttpRequest实现Ajax请求完整步骤

作者:纸飞机时间:2026-11-02 10:27:03浏览:435

本文详解使用原生 JavaScript 的 XMLHttpRequest 对象实现 Ajax 异步请求的完整步骤,涵盖从对象创建、open 初始化、状态监听、send 发送到 GET/POST 传参及异常处理的全部实操细节。

分类:Web 前端技术 技术栈:原生 JavaScript 阅读时长:约 8 分钟
在不重新加载整个网页的情况下与服务器交换数据并局部更新页面,是现代 Web 应用的基础交互方式。虽然现代开发中常使用 Fetch API 或 Axios,但掌握原生 XMLHttpRequest(简称 XHR)是理解浏览器网络通信底层原理的必经之路。本文将严格遵循规范,通过清晰的 4 个关键步骤,演示如何用纯 JavaScript 完成一次可靠的 Ajax 请求,并说明参数配置与状态捕获的具体逻辑。
JavaScript原生XMLHttpRequest实现Ajax请求完整步骤
图 0:XMLHttpRequest 异步客户端与服务端交互架构全景

一、原生 XMLHttpRequest 发起请求的 4 个核心步骤

使用原生 JavaScript 发起一个标准的 Ajax 请求,本质上由四个连续的代码阶段组成:实例化对象、配置请求参数、挂载事件监听器、发送请求数据。

JavaScript原生XMLHttpRequest实现Ajax请求完整步骤
图 1:XMLHttpRequest 原生实现 Ajax 请求的 4 个核心调用步骤

步骤 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 递增。

JavaScript原生XMLHttpRequest实现Ajax请求完整步骤
图 2:XMLHttpRequest readyState 状态迁移与生命周期事件图
状态值 (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 请求。两者的参数传递位置和标头配置具有显著差异:

JavaScript原生XMLHttpRequest实现Ajax请求完整步骤
图 3:GET 与 POST 请求在 XHR 中的配置与传输方式对比

1. 完整 GET 请求代码示例

GET 请求通常通过 URL 传递查询字符串。如果包含中文或特殊符号,必须通过 encodeURIComponent() 进行编码以防乱码:

// 1. 实例化
const xhr = new XMLHttpRequest();

// 2. 拼接 URL 查询参数
const queryParam = encodeURIComponent("前端开发");
const url = `/api/search?keyword=${queryParam}&timestamp=${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 调用必须覆盖完整的异常分支。

JavaScript原生XMLHttpRequest实现Ajax请求完整步骤
图 4:XHR 全生命周期验证与异常排查决策树

核心注意事项与避坑指南

  1. setRequestHeader 的调用时机: setRequestHeader() 必须严格在 open() 之后且在 send() 之前调用。如果在 open() 之前调用,浏览器将直接抛出 DOMException: InvalidStateError。
  2. 跨域资源共享(CORS): 当请求不同源(协议、域名或端口任一不同)的接口时,浏览器会触发同源策略校验。如果服务端没有返回包含 Access-Control-Allow-Origin 的响应头,浏览器会强行拦截响应,触发 onerror 事件且 xhr.status 会表现为 0。如需在跨域请求中携带 Cookie,需在调用 send() 之前设置:
    xhr.withCredentials = true;
  3. 状态码为 0 的场景: 如果 xhr.status === 0,通常意味着网络请求未正常完成。排查顺序应为:本地网络是否连通 > 跨域 CORS 是否被浏览器拦截 > 目标地址是否发生混杂内容拦截(HTTPS 页面中请求 HTTP 资源)。
  4. 手动终止请求: 在用户频繁切换 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 异步编程范式中。

作者最新文章
相关文章
更多