JS-面试必考:手动实现一个标准的 Ajax 请求(XMLHttpRequest)

127 阅读3分钟

前言

虽然 fetch API 在现代开发中非常流行,但 XMLHttpRequest (XHR) 依然是理解 Web 网络编程的基石。无论是底层的 Ajax 封装,还是需要细粒度监控上传/下载进度的场景,XHR 依然不可替代。

一、 什么是 Ajax?

Ajax(Asynchronous JavaScript And XML) 并不是一种单一的编程语言,而是一套技术的组合。其核心是在不重新加载整个网页的情况下,通过与服务器进行异步数据交换,实现页面的局部刷新

  • 核心优势:提升用户体验,减少网络带宽占用。
  • 实现基础:JavaScript 和浏览器原生的 XMLHttpRequest 对象。

二、 XHR 操作的“五步法”逻辑

1. 创建对象

let xhr = new XMLHttpRequest();

2. 注册回调函数 (事件驱动)

XHR 是基于事件驱动的,我们需要在请求发送前定义好如何处理各种状态。

  • onreadystatechange:监控请求的生命周期(状态 0-4)。
  • onerror:处理网络层面的错误(如断网或 DNS 解析失败)。
  • ontimeout:处理响应超时,需配合 xhr.timeout 使用。
  • onprogress:数据传输进度监控。

3. 配置请求参数

通过 open() 方法初始化请求:

  • method:请求方法(GET, POST, PUT 等)。
  • url:请求地址。
  • async:是否异步(默认 true)。注意: 设为 false 会阻塞浏览器主线程,导致页面假死。

4. 设置属性与请求头

  • responseType:设置期望的返回格式(如 json, blob)。
  • setRequestHeader():手动添加自定义请求头(必须在 open 之后,send 之前调用)。

5. 发送请求

  • send(data):正式发起请求。如果是 GET 请求,传 null;如果是 POST,传具体数据。

三、 详解 readyState:五种状态的演变

readyState 是排查 Ajax 问题的关键,它记录了请求的每一步进展:

取值状态名称描述
0UNSENT初始化状态,对象已创建,尚未调用 open()
1OPENED已调用 open(),尚未调用 send(),可设置 Header
2HEADERS_RECEIVED已调用 send(),接收到了响应头和状态码
3LOADING正在下载响应体,responseText 已包含部分数据
4DONE数据接收完成,请求生命周期结束

四、 进度监控:如何实现进度条?

XHR 相比 fetch 的一大优势就是对进度的原生支持。通过 onprogress 事件,我们可以计算出下载百分比:

  • lengthComputable:布尔值,表示服务器是否返回了 Content-Length
  • loaded:已接收的字节数。
  • total:总字节数。

五、 代码实现:封装一个标准的 Ajax 函数

/**
 * 基于 XHR 封装的数据获取函数
 * @param {string} URL 请求地址
 */
function GetWebData(URL) {
  const xhr = new XMLHttpRequest();

  // 1. 状态监控:只在 DONE 阶段处理逻辑
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) { 
      // 兼容性判断:2xx 范围和 304 缓存均认为成功
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        console.log("请求成功:", xhr.response);
      } else {
        console.error("请求失败, 状态码:", xhr.status);
      }
    }
  };

  // 2. 进度监控
  xhr.onprogress = function (event) {
    if (event.lengthComputable) {
      let percent = (event.loaded / event.total) * 100;
      console.log(`下载进度: ${percent.toFixed(2)}%`);
    }
  };

  // 3. 异常与超时处理
  xhr.timeout = 5000; 
  xhr.ontimeout = () => console.error("请求超时!");
  xhr.onerror = () => console.error("网络异常或跨域错误");

  // 4. 配置与发送
  xhr.open("GET", URL, true);
  xhr.responseType = "json"; // 自动解析 JSON
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

  xhr.send(null);
}