郑州市文章资讯

原生Ajax之全面了解xhr的概念与使用

2026-03-27 07:45:02 浏览次数:0
详细信息
原生 Ajax:全面了解 XMLHttpRequest (XHR)

一、XHR 概述

XMLHttpRequest (XHR) 是浏览器提供的 JavaScript API,用于在不刷新页面的情况下与服务器交换数据,是实现 Ajax 技术的核心。

1.1 XHR 的发展历程

二、创建 XHR 对象

// 现代浏览器
const xhr = new XMLHttpRequest();

// 兼容旧版 IE (IE5/IE6)
let xhr;
if (window.XMLHttpRequest) {
  xhr = new XMLHttpRequest();
} else {
  // 旧版 IE
  xhr = new ActiveXObject("Microsoft.XMLHTTP");
}

三、XHR 的基本使用

3.1 发送 GET 请求

function getData() {
  const xhr = new XMLHttpRequest();

  // 配置请求
  xhr.open('GET', 'https://api.example.com/data?id=123', true);

  // 设置请求头(可选)
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.setRequestHeader('Authorization', 'Bearer token123');

  // 响应处理
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        console.log('成功:', xhr.responseText);
      } else {
        console.error('错误:', xhr.status);
      }
    }
  };

  // 发送请求
  xhr.send();
}

3.2 发送 POST 请求

function postData() {
  const xhr = new XMLHttpRequest();
  const data = {
    name: 'John',
    age: 30
  };

  xhr.open('POST', 'https://api.example.com/users', true);
  xhr.setRequestHeader('Content-Type', 'application/json');

  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.status < 300) {
        console.log('创建成功:', xhr.responseText);
      } else {
        console.error('创建失败:', xhr.status);
      }
    }
  };

  // 发送 JSON 数据
  xhr.send(JSON.stringify(data));
}

四、XHR 对象的重要属性和方法

4.1 属性

属性 描述
readyState 请求状态:0=未初始化,1=已打开,2=已发送,3=接收中,4=完成
status HTTP 状态码(200=成功,404=未找到等)
statusText HTTP 状态文本
responseText 响应文本(字符串格式)
responseXML 响应 XML 文档(如果内容类型正确)
response 根据 responseType 返回响应体
responseType 设置响应类型:""、"text"、"json"、"document"等
timeout 请求超时时间(毫秒)
withCredentials 是否发送跨域凭据(cookies等)

4.2 方法

方法 描述
open(method, url, async) 初始化请求
send(body) 发送请求
abort() 中止请求
setRequestHeader(header, value) 设置请求头
getResponseHeader(header) 获取响应头
getAllResponseHeaders() 获取所有响应头

4.3 事件

事件 描述
onreadystatechange readyState 变化时触发
onload 请求完成时触发
onerror 请求失败时触发
onprogress 接收数据时周期性触发
ontimeout 请求超时时触发
onloadstart 请求开始时触发
onloadend 请求结束时触发(无论成功失败)

五、XHR 生命周期与 readyState

function trackXHRStates() {
  const xhr = new XMLHttpRequest();

  xhr.onreadystatechange = function() {
    console.log(`readyState: ${xhr.readyState}`);

    switch(xhr.readyState) {
      case 0: // UNSENT
        console.log('请求未初始化,open() 尚未调用');
        break;
      case 1: // OPENED
        console.log('请求已建立,open() 已调用');
        break;
      case 2: // HEADERS_RECEIVED
        console.log('请求已发送,send() 已调用,响应头已接收');
        console.log('状态码:', xhr.status);
        break;
      case 3: // LOADING
        console.log('响应体下载中');
        console.log('已接收数据长度:', xhr.responseText.length);
        break;
      case 4: // DONE
        console.log('请求完成');
        if (xhr.status === 200) {
          console.log('成功接收数据');
        }
        break;
    }
  };

  xhr.open('GET', 'https://api.example.com/data', true);
  xhr.send();
}

六、处理不同类型的响应

6.1 处理 JSON 响应

function getJSON() {
  const xhr = new XMLHttpRequest();

  xhr.open('GET', 'https://api.example.com/data.json', true);

  // 方法1:使用 responseType 自动解析
  xhr.responseType = 'json';

  xhr.onload = function() {
    if (xhr.status === 200) {
      // response 已经是 JavaScript 对象
      console.log(xhr.response);
    }
  };

  // 方法2:手动解析 JSON
  xhr.onload = function() {
    if (xhr.status === 200) {
      try {
        const data = JSON.parse(xhr.responseText);
        console.log(data);
      } catch (error) {
        console.error('JSON 解析错误:', error);
      }
    }
  };

  xhr.send();
}

6.2 处理 XML 响应

function getXML() {
  const xhr = new XMLHttpRequest();

  xhr.open('GET', 'https://api.example.com/data.xml', true);
  xhr.responseType = 'document';

  xhr.onload = function() {
    if (xhr.status === 200) {
      const xmlDoc = xhr.responseXML;
      const items = xmlDoc.getElementsByTagName('item');

      for (let i = 0; i < items.length; i++) {
        console.log(items[i].textContent);
      }
    }
  };

  xhr.send();
}

七、高级功能

7.1 超时设置

function requestWithTimeout() {
  const xhr = new XMLHttpRequest();

  xhr.open('GET', 'https://api.example.com/slow-data', true);
  xhr.timeout = 5000; // 5秒超时

  xhr.ontimeout = function() {
    console.error('请求超时');
  };

  xhr.onload = function() {
    if (xhr.status === 200) {
      console.log('数据:', xhr.responseText);
    }
  };

  xhr.send();
}

7.2 进度监控

function trackProgress() {
  const xhr = new XMLHttpRequest();

  xhr.open('GET', 'https://api.example.com/large-file', true);

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

  xhr.onloadstart = function() {
    console.log('开始下载');
  };

  xhr.onloadend = function() {
    console.log('下载结束');
  };

  xhr.send();
}

7.3 上传进度监控

function uploadWithProgress() {
  const xhr = new XMLHttpRequest();
  const fileInput = document.getElementById('fileInput');
  const file = fileInput.files[0];
  const formData = new FormData();

  formData.append('file', file);

  xhr.open('POST', 'https://api.example.com/upload', true);

  // 监控上传进度
  xhr.upload.onprogress = function(event) {
    if (event.lengthComputable) {
      const percentComplete = (event.loaded / event.total) * 100;
      console.log(`上传进度: ${percentComplete.toFixed(2)}%`);
    }
  };

  xhr.onload = function() {
    if (xhr.status === 200) {
      console.log('上传成功');
    }
  };

  xhr.send(formData);
}

八、封装 XHR 为 Promise

function xhrPromise(method, url, data = null, headers = {}) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.open(method, url, true);

    // 设置请求头
    Object.keys(headers).forEach(key => {
      xhr.setRequestHeader(key, headers[key]);
    });

    xhr.onload = function() {
      if (xhr.status >= 200 && xhr.status < 300) {
        try {
          // 尝试解析 JSON
          const response = xhr.responseText ? JSON.parse(xhr.responseText) : null;
          resolve({
            status: xhr.status,
            data: response,
            headers: xhr.getAllResponseHeaders()
          });
        } catch (error) {
          // 如果不是 JSON,返回原始文本
          resolve({
            status: xhr.status,
            data: xhr.responseText,
            headers: xhr.getAllResponseHeaders()
          });
        }
      } else {
        reject({
          status: xhr.status,
          statusText: xhr.statusText
        });
      }
    };

    xhr.onerror = function() {
      reject({
        status: xhr.status,
        statusText: xhr.statusText
      });
    };

    xhr.ontimeout = function() {
      reject(new Error('请求超时'));
    };

    // 发送数据
    if (data && typeof data === 'object' && !(data instanceof FormData)) {
      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.send(JSON.stringify(data));
    } else {
      xhr.send(data);
    }
  });
}

// 使用示例
xhrPromise('GET', 'https://api.example.com/users')
  .then(response => console.log('成功:', response))
  .catch(error => console.error('失败:', error));

九、实际应用示例

9.1 完整的数据请求模块

class ApiService {
  constructor(baseURL = '') {
    this.baseURL = baseURL;
  }

  request(config) {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      const url = this.baseURL + config.url;

      xhr.open(config.method || 'GET', url, true);

      // 设置请求头
      const headers = {
        'Content-Type': 'application/json',
        ...config.headers
      };

      Object.keys(headers).forEach(key => {
        xhr.setRequestHeader(key, headers[key]);
      });

      // 设置超时
      if (config.timeout) {
        xhr.timeout = config.timeout;
      }

      // 响应类型
      if (config.responseType) {
        xhr.responseType = config.responseType;
      }

      // 事件处理
      xhr.onload = () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          let responseData;

          try {
            responseData = xhr.responseType === 'json' 
              ? xhr.response 
              : JSON.parse(xhr.responseText);
          } catch {
            responseData = xhr.responseText;
          }

          resolve({
            data: responseData,
            status: xhr.status,
            headers: this.parseHeaders(xhr.getAllResponseHeaders())
          });
        } else {
          reject(this.createError(xhr));
        }
      };

      xhr.onerror = () => reject(this.createError(xhr));
      xhr.ontimeout = () => reject(new Error('请求超时'));

      // 发送数据
      const data = config.data || config.body;
      xhr.send(data ? JSON.stringify(data) : null);
    });
  }

  parseHeaders(headersString) {
    const headers = {};
    const lines = headersString.trim().split(/[\r\n]+/);

    lines.forEach(line => {
      const parts = line.split(': ');
      const header = parts.shift();
      const value = parts.join(': ');
      headers[header] = value;
    });

    return headers;
  }

  createError(xhr) {
    const error = new Error(`请求失败: ${xhr.status} ${xhr.statusText}`);
    error.status = xhr.status;
    error.statusText = xhr.statusText;
    return error;
  }

  get(url, config = {}) {
    return this.request({ ...config, method: 'GET', url });
  }

  post(url, data, config = {}) {
    return this.request({ ...config, method: 'POST', url, data });
  }

  put(url, data, config = {}) {
    return this.request({ ...config, method: 'PUT', url, data });
  }

  delete(url, config = {}) {
    return this.request({ ...config, method: 'DELETE', url });
  }
}

// 使用示例
const api = new ApiService('https://api.example.com');

api.get('/users')
  .then(response => console.log('用户列表:', response.data))
  .catch(error => console.error('错误:', error));

api.post('/users', { name: 'Alice', age: 25 })
  .then(response => console.log('创建成功:', response.data))
  .catch(error => console.error('错误:', error));

十、XHR 与 Fetch API 对比

特性 XMLHttpRequest Fetch API
兼容性 非常好(包括 IE7+) 较好(不兼容 IE)
Promise 支持 需要手动封装 原生支持 Promise
请求取消 支持(abort()) 支持(AbortController)
进度监控 支持(upload/onprogress) 有限支持(Response.body)
超时设置 原生支持(timeout) 需要手动实现
CORS 处理 需要特殊处理 更简单直观
流式读取 不支持 支持(Response.body)

十一、最佳实践

错误处理:始终处理 onerror 和 status 检查 超时设置:为长时间请求设置合理的超时 取消请求:在组件卸载时取消未完成的请求 进度反馈:为大文件上传/下载提供进度提示 封装重用:封装通用 XHR 函数,避免重复代码 安全考虑:验证和清理服务器响应数据

十二、总结

虽然现代开发中更推荐使用 Fetch API 或 axios 等库,但理解 XHR 的工作原理对于深入理解浏览器网络请求机制非常重要。XHR 提供了丰富的控制和事件,在某些场景下(如需要精确进度监控或更好的浏览器兼容性)仍然是合适的选择。

掌握 XHR 有助于你:

相关推荐