引言:无刷新时代的开端
在 Web 1.0 时代,每次用户操作几乎都需要重新加载整个页面。而今天,我们早已习惯点击“加载更多”时内容悄然更新、搜索框实时显示建议、表单提交后局部提示成功——这一切的背后,都离不开一项关键技术:Ajax(Asynchronous JavaScript and XML) 。
尽管现代开发中我们常使用 fetch 或 Axios 等高级封装,但理解其底层核心——XMLHttpRequest(XHR),仍是掌握 Web 数据交互本质的关键。本文将结合代码实例,深入剖析 Ajax 的完整工作流程、状态变化机制及实战注意事项。
一、什么是 Ajax?
Ajax 并非一种新语言,而是一种编程模式,其核心思想是:
通过 JavaScript 在后台异步发起 HTTP 请求,获取数据后动态更新页面局部内容,无需整页刷新。
虽然名称中包含 “XML”,但如今绝大多数接口返回的是 JSON 格式数据。因此,Ajax 的实际含义更贴近 “异步 JavaScript 和 JSON”。
二、Ajax 的四大核心步骤
根据笔记所述,一个完整的 Ajax 请求包含四个关键阶段:
2.1 实例化:创建请求对象
const xhr = new XMLHttpRequest();
XMLHttpRequest 是浏览器内置的构造函数,用于创建一个可发送 HTTP 请求的对象。此时,xhr.readyState 为 0,表示“未初始化”。
2.2 配置请求:open 方法
xhr.open('GET', 'https://api.github.com/orgs/lemoncode/members', false);
open 方法用于配置请求参数:
- method:HTTP 方法(如
GET、POST) - url:目标接口地址
- async:是否异步(
true为异步,false为同步)
⚠️ 注意:代码中使用了
false(同步),这会导致主线程阻塞,直到请求完成。现代开发中强烈不推荐同步请求,应始终使用true(默认值)。
调用 open 后,readyState 变为 1(已调用 open)。
2.3 发送请求:send 方法
xhr.send();
send() 真正将请求发出。若为 GET 请求,通常无需传参;若为 POST,可在此传递请求体数据。
调用后,readyState 立即变为 2(已调用 send)。
2.4 监听响应:onreadystatechange 事件
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
// 更新 DOM
}
};
这是 Ajax 的核心回调机制。每当请求状态发生变化,该函数就会被触发。
三、深入理解 readyState:请求的生命周期
readyState 是 XHR 对象的关键属性,它用数字表示请求所处的阶段:
| 值 | 状态 | 说明 |
|---|---|---|
| 0 | UNSENT | 已创建 XHR 对象,但未调用 open() |
| 1 | OPENED | 已调用 open(),请求已初始化 |
| 2 | HEADERS_RECEIVED | 已调用 send(),且已接收到响应头 |
| 3 | LOADING | 正在接收响应体(可能未完成) |
| 4 | DONE | 响应完成,可安全读取 responseText |
只有当 readyState === 4 且 status === 200(或其他成功状态码如 201)时,才能认为请求成功完成。
💡 提示:代码中在
send()后立即打印readyState,由于是同步请求,此时值已是 4;若为异步,则仍为 2。
四、处理响应:从文本到 DOM 更新
4.1 获取响应数据
服务器返回的数据以字符串形式存储在 xhr.responseText 中:
console.log(xhr.responseText); // '[{"login":"antonio06"},...]'
由于接口返回的是 JSON 字符串,需用 JSON.parse() 转换为 JavaScript 对象:
const data = JSON.parse(xhr.responseText);
4.2 动态更新页面
获得数据后,即可操作 DOM 实现局部更新:
document.getElementById('members').innerHTML = data.map(item => `
<li>${item.login}</li>
`).join('');
注意:原代码中错误地使用了 <tr><li>...</li></tr>,<tr> 应用于表格行,而 <ul> 应直接包含 <li>。正确写法应为:
<li>${item.login}</li>
这种“请求 → 解析 → 渲染”的模式,正是 Ajax 实现动态交互的核心逻辑。
五、同步 vs 异步:为何必须选择异步?
代码中使用了同步请求(async: false),这会带来严重问题:
xhr.open('GET', url, false); // 同步
xhr.send(); // 主线程在此阻塞,直到响应返回
console.log("这行代码要等请求完成后才执行");
后果包括:
- 页面完全卡死,无法滚动、点击
- 用户体验极差
- 浏览器可能弹出“脚本无响应”警告
而异步请求(async: true,默认)则不会阻塞主线程:
xhr.open('GET', url); // 默认 async=true
xhr.send();
console.log("这行代码立即执行"); // 不等待请求
xhr.onreadystatechange = () => { /* 响应到达时再处理 */ };
✅ 最佳实践:永远使用异步请求!
六、Ajax 的局限与演进
尽管 XMLHttpRequest 功能强大,但它也存在明显缺点:
- API 设计陈旧,回调嵌套易导致“回调地狱”
- 错误处理不够直观
- 不支持 Promise,难以链式调用
因此,ES6 引入了更现代的 fetch API:
fetch('https://api.github.com/orgs/lemoncode/members')
.then(res => res.json())
.then(data => {
document.getElementById('members').innerHTML =
data.map(item => `<li>${item.login}</li>`).join('');
});
fetch 基于 Promise,代码更简洁、可读性更强。但理解 XHR 仍是必要的——因为 fetch 本质上是对 XHR 的封装,且许多老项目仍在使用 XHR。
七、安全与兼容性注意事项
- 跨域问题
若请求的域名、端口或协议与当前页面不同,浏览器会因同源策略阻止请求。解决方法包括 CORS(服务端配置)或代理。 - 错误处理
必须检查xhr.status,而非仅依赖readyState === 4。网络错误(如 404、500)也会使readyState达到 4。 - 内存泄漏
长时间运行的页面应避免频繁创建 XHR 对象而不释放引用。
结语:Ajax 是动态 Web 的基石
从 XMLHttpRequest 的五个状态,到 responseText 的解析,再到 DOM 的精准更新,Ajax 构建了现代 Web 应用的交互骨架。尽管高级工具层出不穷,但回归底层,理解“请求如何发出、状态如何变化、数据如何流转”,才能在复杂场景中游刃有余。
正如开篇所定义:Ajax = 异步 + JavaScript + 数据交换。
掌握它,就掌握了让网页“活”起来的第一把钥匙。