XMLHttpRequest 与 AJAX
本章定位 :掌握 XMLHttpRequest 对象的核心机制——readyState 生命周期、responseXML 解析、异步回调模型,理解 AJAX 如何实现"无刷新更新页面"。
定义与作用
XMLHttpRequest (简称 XHR)是所有现代浏览器内置的一个 JavaScript 对象,用于在不刷新页面的情况下与服务器交换数据。它是 AJAX 技术的基石。
AJAX (Asynchronous JavaScript and XML)并非一种新技术,而是:
- JavaScript 发起异步请求
- XMLHttpRequest 对象作为通信载体
- XML (或 JSON)作为数据交换格式
- DOM 动态更新页面内容
四项技术的组合,让网页应用具备了"桌面应用般的流畅体验"。在 AJAX 出现之前,任何数据更新都需要整页刷新——这就像每次修改文档中的一个字都要把整本笔记本重新抄写一遍。
核心原理:XHR 生命周期
图解释 :XMLHttpRequest 对象经历 5 个 readyState 阶段(0→1→2→3→4)。每次状态变化都触发 onreadystatechange 回调。只有当 readyState=4 且 HTTP status=200 时,才表示服务器成功返回了完整数据。
语法/结构要点
XHR 核心属性与方法
| 属性/方法 | 说明 |
|---|---|
readyState | 请求状态:0=UNSENT, 1=OPENED, 2=HEADERS_RECEIVED, 3=LOADING, 4=DONE |
status | HTTP 状态码:200=成功, 404=未找到, 500=服务器错误 |
responseText | 以字符串形式返回服务器响应 |
responseXML | 以 XML DOM 对象形式返回服务器响应(Content-Type: text/xml 时才有值) |
onreadystatechange | 状态变化时的回调函数 |
open(method, url, async) | 初始化请求:method=GET/POST, url=请求地址, async=true(异步)/false(同步) |
send(data) | 发送请求。GET 请求传 null;POST 请求传请求体数据 |
setRequestHeader(name, value) | 设置 HTTP 请求头(必须在 open 之后、send 之前调用) |
readyState 详解
| readyState | 常量 | 含义 | 此时可用 |
|---|---|---|---|
| 0 | UNSENT | 对象已创建,open() 未调用 | 无 |
| 1 | OPENED | open() 已调用 | setRequestHeader() |
| 2 | HEADERS_RECEIVED | 收到响应头 | status, getResponseHeader() |
| 3 | LOADING | 正在接收响应体 | responseText(可能不完整) |
| 4 | DONE | 响应完成 | responseText, responseXML(完整) |
responseXML 的前提条件
responseXML 返回一个可被 DOM API 操作的 XML Document 对象,但需满足:
- 服务器响应的
Content-Type必须是text/xml或application/xml - 服务器返回的 XML 必须是 格式良好 的(Well-Formed)
- 如果 XML 有语法错误,
responseXML将是null
完整示例
场景说明
飞翔科技的前端 小崔 正在开发一个"项目进度查询"页面。产品经理要求在搜索框中输入项目 ID,页面需要实时显示该项目详情——不能刷新整个页面 。
后端已做好接口:GET /api/project?id=P001 返回 XML 格式的项目数据。
操作前:传统的整页刷新方式
<!-- 传统方式:form 提交导致整页刷新 -->
<form action="/api/project" method="get">
<input type="text" name="id" placeholder="输入项目ID" />
<button type="submit">查询</button>
</form>
<div id="result">
<!-- 整页刷新后才能看到结果 -->
</div>
每次查询都要刷新页面,体验割裂。用户的滚动位置丢失、其他输入框内容清空。
应用 AJAX 后
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>飞翔科技 - 项目查询</title>
<style>
#result { margin-top: 20px; padding: 15px; border: 1px solid #ddd; }
.loading { color: #ff9800; }
.error { color: #f44336; }
</style>
</head>
<body>
<h2>项目进度查询</h2>
<input type="text" id="projectId" placeholder="输入项目ID(如 P001)" />
<button onclick="queryProject()">查询</button>
<div id="result"></div>
<script>
function queryProject() {
var id = document.getElementById("projectId").value.trim();
if (!id) {
document.getElementById("result").innerHTML
= '<span class="error">请输入项目ID</span>';
return;
}
var resultDiv = document.getElementById("result");
resultDiv.innerHTML = '<span class="loading">查询中...</span>';
// 1. 创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
// 2. 配置请求
xhr.open("GET", "/api/project?id=" + encodeURIComponent(id), true);
// 3. 设置回调 — 这是异步模型的核心
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) { // 响应完成
if (xhr.status === 200) { // HTTP 成功
// 4. 解析 XML 响应
var xmlDoc = xhr.responseXML;
if (xmlDoc) {
renderProject(xmlDoc, resultDiv);
} else {
resultDiv.innerHTML = '<span class="error">响应格式错误</span>';
}
} else if (xhr.status === 404) {
resultDiv.innerHTML
= '<span class="error">项目 ' + id + ' 不存在</span>';
} else {
resultDiv.innerHTML
= '<span class="error">服务器错误(' + xhr.status + ')</span>';
}
}
};
// 5. 发送请求
xhr.send(null);
}
function renderProject(xmlDoc, container) {
// 使用 DOM API 从 XML 中提取数据
var name = xmlDoc.getElementsByTagName("name")[0].textContent;
var owner = xmlDoc.getElementsByTagName("owner")[0].textContent;
var deadline = xmlDoc.getElementsByTagName("deadline")[0].textContent;
var status = xmlDoc.getElementsByTagName("status")[0].textContent;
var budget = xmlDoc.getElementsByTagName("budget")[0].textContent;
var statusColor = status === "已完成" ? "#04AA6D"
: status === "进行中" ? "#ff9800" : "#2196F3";
container.innerHTML =
'<table>' +
' <tr><td><strong>项目名称</strong></td><td>' + name + '</td></tr>' +
' <tr><td><strong>负责人</strong></td><td>' + owner + '</td></tr>' +
' <tr><td><strong>截止日期</strong></td><td>' + deadline + '</td></tr>' +
' <tr><td><strong>状态</strong></td>' +
' <td style="color:' + statusColor + ';font-weight:bold">'
+ status + '</td></tr>' +
' <tr><td><strong>预算</strong></td><td>¥' + budget + '</td></tr>' +
'</table>';
}
</script>
</body>
</html>
服务器端返回的 XML 示例
当请求 GET /api/project?id=P001 时,服务器返回:
<?xml version="1.0" encoding="UTF-8"?>
<project id="P001">
<name>智能工单系统</name>
<owner>大翔</owner>
<deadline>2026-05-15</deadline>
<status>进行中</status>
<budget>500000</budget>
</project>
操作结果
| 对比维度 | 传统方式 | AJAX 方式 |
|---|---|---|
| 页面刷新 | 整页刷新,闪烁、滚动丢失 | 无刷新 ,页面保持原位 |
| 用户体验 | 割裂感、等待白屏 | 即时反馈,loading 提示流畅 |
| 其他输入 | 所有表单输入丢失 | 保留所有已填写内容 |
| 网络开销 | 重新加载整个页面(HTML+CSS+JS) | 仅传输 XML 数据(几十字节) |
| 可并发 | 一次只能处理一个请求 | 可同时发起多个异步请求 |
易错场景
在 readyState !== 4 时读取 responseXML
// 错误:可能拿到不完整数据
xhr.onreadystatechange = function() {
var xml = xhr.responseXML; // readyState=2/3 时可能为 null
console.log(xml.getElementsByTagName("name")); // TypeError!
};
正确 :始终先判断 xhr.readyState === 4 && xhr.status === 200。
混淆同步和异步模式
// 错误:第三个参数设为 false,变成同步请求
xhr.open("GET", url, false);
xhr.send(null);
// 此时 JS 线程被阻塞,页面卡死,直到响应返回
同步 XHR 已被主流浏览器标记为 废弃 。始终使用 true(异步模式),通过回调处理结果。
responseXML 为 null
这通常是因为服务器没有设置正确的 Content-Type:
// 后端忘记设置 Content-Type
response.getWriter().write(xmlString); // 默认 text/html,responseXML 为 null
后端必须设置:response.setContentType("text/xml") 或 "application/xml"。
面试考点
| 考点 | 参考答案要点 |
|---|---|
| AJAX 的全称和四大组成部分? | Asynchronous JavaScript and XML。JavaScript + XMLHttpRequest + XML/JSON 数据 + DOM 操作 |
| readyState 的 5 个值及含义? | 0=UNSENT(未调用open), 1=OPENED(已调用open), 2=HEADERS_RECEIVED(收到响应头), 3=LOADING(接收中), 4=DONE(完成) |
| responseText 与 responseXML 的区别? | responseText 返回字符串(适用任何响应类型);responseXML 返回 XML DOM 对象(仅当 Content-Type 为 XML 且格式良好时) |
| 为什么同步 XHR 已被废弃? | 阻塞 JS 主线程导致页面卡死、用户体验极差。现代方案用 Promise/fetch 或 async/await |
| 如何判断 AJAX 请求成功? | readyState === 4 且 status 在 200~299 范围内 |