AJAX 教程
AJAX(Asynchronous JavaScript And XML,异步 JavaScript 与 XML)是一种在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。它让网页从"整页刷新"进化到"局部更新",是构建现代富交互 Web 应用的基础。
为什么需要 AJAX
传统网页流程是"提交表单 → 整页刷新 → 等待渲染",用户操作被一次次打断。AJAX 把请求放到后台异步执行,页面保持可交互,数据返回后只更新需要的部分,体验大幅提升。
| 对比项 | 传统方式 | AJAX |
|---|---|---|
| 页面刷新 | 整页刷新 | 局部更新 |
| 用户体验 | 频繁等待、闪烁 | 流畅、无中断 |
| 数据传输 | 整页 HTML | 仅需要的数据 |
| 带宽消耗 | 高 | 低 |
核心组成
AJAX 并非单一技术,而是多种技术的组合:
- XMLHttpRequest(XHR):浏览器内置的异步通信对象
- DOM:动态更新页面内容
- CSS:改变样式
- JSON / XML:数据交换格式(如今 JSON 占绝对主流)
- JavaScript:把一切串联起来的胶水语言
现代替代方案
如今更推荐使用 Fetch API(基于 Promise,语法更简洁)或 axios 等库。但 XHR 仍然是理解异步请求原理的最佳切入点,本教程会同时介绍两者。
AJAX 的 "X" 虽然代表 XML,但现在几乎都用 JSON 传输数据,名字只是一个历史遗留。
AJAX 简介
AJAX(Asynchronous JavaScript and XML)是一种创建快速动态网页的技术。它允许网页在不重新加载整个页面的情况下,与服务器交换数据并更新部分内容。
异步的含义
"异步"意味着请求发出后,JavaScript 代码不必等待服务器响应,可以继续执行其他任务;响应返回后再通过回调函数处理。这与传统同步请求(阻塞等待)形成鲜明对比。
// 同步请求:代码阻塞等待
// 异步请求:发出后立即返回,响应到达时再处理
console.log("请求发出");
// 不阻塞,继续执行
console.log("页面仍然流畅可交互");
工作原理
一次典型的 AJAX 交互包含以下步骤:
- 浏览器触发事件(如点击按钮)
- JavaScript 创建 XMLHttpRequest 对象
- XHR 向服务器发送请求(异步,不阻塞 UI)
- 服务器处理请求并返回数据(JSON / XML / 文本)
- 浏览器通过回调拿到数据,用 DOM 操作更新页面
示例场景
- 搜索框输入时实时联想(如百度、Google)
- 登录表单校验,不用跳页
- 点赞/关注,局部数字更新
- 无限滚动加载更多内容
AJAX 受同源策略限制:默认只能请求同协议、同域名、同端口的资源。跨域请求需要服务器端配合 CORS 或 JSONP。
AJAX 实例
通过一个完整可运行的实例,直观感受 AJAX 的工作流程:点击按钮,向服务器请求一个文本文件,然后把它显示到页面上,全程无需刷新页面。
HTML 页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AJAX 入门实例</title>
</head>
<body>
<div id="demo">
<h2>点击按钮修改这段内容</h2>
<button type="button">请求数据</button>
</div>
<script>
function loadDoc() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerHTML = this.responseText;
}
};
xhr.open("GET", "demo.txt", true);
xhr.send();
}
</script>
</body>
</html>
服务器文件 demo.txt
这是服务器返回的新内容!
AJAX 请求成功,页面无需刷新。</code>
代码逐行解读
| 代码 | 作用 |
|---|---|
new XMLHttpRequest() | 创建异步请求对象 |
onreadystatechange | 监听请求状态变化 |
readyState == 4 | 请求已完成 |
status == 200 | 响应成功(HTTP 200) |
open("GET", url, true) | 配置请求方式与地址,true=异步 |
send() | 发送请求 |
innerHTML | 用响应内容更新页面元素 |
把 open() 的第三个参数改为 false 即为同步模式,但现代浏览器已不推荐(会阻塞主线程,影响交互)。
XHR 创建对象
XMLHttpRequest(简称 XHR)是浏览器内置的、用于与服务器交互的核心对象,所有传统 AJAX 操作都从创建 XHR 对象开始。
标准创建方式
var xhr = new XMLHttpRequest();
兼容旧版 IE
IE6/IE7 使用 ActiveX 方式创建,早期代码常写成下面这样保证兼容:
var xhr;
if (window.XMLHttpRequest) {
// 现代浏览器
xhr = new XMLHttpRequest();
} else {
// IE5/IE6 兼容
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
如今 IE 早已淘汰,标准写法即可,但理解这段历史有助于阅读老代码。
XHR 对象的常用属性
| 属性 | 说明 |
|---|---|
readyState | 请求状态(0-4),4 表示完成 |
status | HTTP 状态码(200 成功 / 404 未找到等) |
statusText | 状态文本(如 "OK") |
responseText | 以字符串形式返回的响应数据 |
responseXML | 以 XML 文档形式返回的响应(较少用) |
onreadystatechange | 状态变化时触发的事件处理器 |
常用方法
| 方法 | 说明 |
|---|---|
open(method, url, async) | 初始化请求(方法、地址、是否异步) |
send(body) | 发送请求,GET 传 null,POST 传数据体 |
setRequestHeader(key, val) | 设置请求头(POST 常用) |
abort() | 中止请求 |
现代开发中更推荐 fetch(),但 XHR 支持进度事件(onprogress)和请求中止等更细粒度控制,某些场景仍是首选。
XHR 请求
创建 XHR 对象后,通过 open() 配置请求、send() 发送请求。本节介绍 GET 与 POST 两种最常用的请求方式。
GET 请求
GET 适合请求数据,参数附加在 URL 查询串中:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
};
xhr.open("GET", "https://api.example.com/users?id=1&type=admin", true);
xhr.send();
POST 请求
POST 适合提交数据,数据放在请求体中,并显式声明 Content-Type:
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://api.example.com/users", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
};
xhr.send("name=张三&age=25");
GET 与 POST 对比
| 对比项 | GET | POST |
|---|---|---|
| 数据位置 | URL 查询串 | 请求体 |
| 数据大小 | 有限(约 2KB) | 较大 |
| 安全性 | 低(URL 可见/可缓存) | 较高 |
| 是否可缓存 | 可被缓存 | 默认不缓存 |
| 典型场景 | 查询、获取列表 | 登录、提交表单、上传 |
发送 JSON 数据(POST)
var xhr = new XMLHttpRequest();
xhr.open("POST", "/api/save", true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.onload = function() {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.send(JSON.stringify({ name: "李四", age: 30 }));
用 send() 发送对象时,必须先 JSON.stringify() 序列化,否则会变成字符串 [object Object]。
XHR 响应
请求发出后,服务器返回的数据会保存在 XHR 对象的 responseText 或 responseXML 属性中,供回调函数读取使用。
使用 responseText
当服务器返回普通文本或 JSON 字符串时,用 responseText:
var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/notice", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// 直接作为文本使用
document.getElementById("notice").innerHTML = xhr.responseText;
// 若是 JSON 字符串,先解析再使用
var data = JSON.parse(xhr.responseText);
console.log(data.title);
}
};
xhr.send();
使用 responseXML
当服务器返回 XML 文档时(响应头 Content-Type: text/xml),用 responseXML 并通过 DOM 方法解析:
var xhr = new XMLHttpRequest();
xhr.open("GET", "/data/books.xml", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var xmlDoc = xhr.responseXML;
var titles = xmlDoc.getElementsByTagName("title");
for (var i = 0; i < titles.length; i++) {
console.log(titles[i].childNodes[0].nodeValue);
}
}
};
xhr.send();
统一处理响应
var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/user", true);
xhr.onload = function() { // onload:请求成功完成
if (xhr.status === 200) {
console.log("成功:", xhr.responseText);
} else {
console.error("HTTP 错误:", xhr.status);
}
};
xhr.onerror = function() { // 网络错误
console.error("网络请求失败");
};
xhr.send();
现代写法推荐用 onload/onerror 代替繁琐的 onreadystatechange 状态判断,代码更清晰。
XHR readyState
readyState 属性表示 XMLHttpRequest 请求的当前状态,取值 0-4,每次变化都会触发 onreadystatechange 事件。
五种状态
| 值 | 状态 | 含义 |
|---|---|---|
| 0 | UNSENT | 已创建对象,但 open() 尚未调用 |
| 1 | OPENED | 已调用 open(),请求未发送 |
| 2 | HEADERS_RECEIVED | 已调用 send(),响应头已接收 |
| 3 | LOADING | 响应体下载中 |
| 4 | DONE | 请求完成,响应已就绪 |
监听状态变化
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
console.log("当前状态:", xhr.readyState);
if (xhr.readyState == 4) { // 请求完成
if (xhr.status == 200) { // 且 HTTP 成功
console.log("数据:", xhr.responseText);
} else {
console.error("错误码:", xhr.status);
}
}
};
xhr.open("GET", "/api/data", true);
xhr.send();
readyState 与 status 的区别
- readyState:请求过程的状态(传输阶段)
- status:HTTP 响应状态码(服务器返回结果)
两者缺一不可:只有 readyState == 4(传输完成)且 status == 200(服务器成功处理)才算一次成功的请求。
简化的现代写法
var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data", true);
xhr.onload = function() {
// onload 触发时 readyState 必为 4,只需判断 status
if (xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();
调试时在 onreadystatechange 里打印 readyState,可以直观看到请求的生命周期。
AJAX ASP/PHP
AJAX 最典型的应用之一,是与服务端脚本(如 PHP、ASP、JSP)配合:前端发请求,服务端读取数据库或执行逻辑后返回结果。本节以 PHP 为例。
请求 PHP 文件
前端通过 GET 携带参数请求 PHP,PHP 处理后输出文本/JSON:
<!DOCTYPE html>
<html>
<body>
<form>
姓名:<input type="text" id="name">
<button type="button">查询</button>
</form>
<p id="result"></p>
<script>
function send() {
var name = document.getElementById("name").value;
var xhr = new XMLHttpRequest();
xhr.open("GET", "hello.php?name=" + encodeURIComponent(name), true);
xhr.onload = function() {
if (xhr.status === 200) {
document.getElementById("result").innerHTML = xhr.responseText;
}
};
xhr.send();
}
</script>
</body>
</html>
服务端 hello.php
<?php
// 接收参数并做基本校验
$name = isset($_GET['name']) ? htmlspecialchars($_GET['name']) : '游客';
// 模拟查询数据库
$users = ['张三' => '北京', '李四' => '上海'];
if (isset($users[$name])) {
echo "{$name} 所在城市:" . $users[$name];
} else {
echo "未找到用户:{$name}";
}
?>
返回 JSON(推荐)
服务端直接输出 JSON,前端解析后灵活渲染:
<?php
header('Content-Type: application/json; charset=utf-8');
$name = $_GET['name'] ?? '';
// 模拟数据
$data = ['status' => 'ok', 'city' => '北京', 'age' => 25];
echo json_encode($data, JSON_UNESCAPED_UNICODE);
?>
// 前端解析 JSON
xhr.onload = function() {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
console.log(data.city); // 北京
}
};
服务端输出必须只有数据,不能混入 HTML、空格或 BOM 头,否则前端 JSON.parse 会报错。
AJAX 数据库
AJAX 与数据库结合是动态网站的核心模式:前端异步请求 → 服务端查询数据库 → 返回结果 → 局部刷新页面。
完整流程
- 用户操作触发 AJAX 请求
- 服务端脚本连接数据库执行 SQL 查询
- 查询结果转为 JSON 返回前端
- 前端渲染数据,更新页面局部
前端请求示例
<select id="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
<div id="userList"></div>
<script>
function loadUsers() {
var city = document.getElementById("city").value;
var xhr = new XMLHttpRequest();
xhr.open("GET", "get_users.php?city=" + city, true);
xhr.onload = function() {
if (xhr.status === 200) {
var users = JSON.parse(xhr.responseText);
renderList(users);
}
};
xhr.send();
}
function renderList(users) {
var html = "<ul>";
users.forEach(function(u) {
html += "<li>" + u.name + " - " + u.phone + "</li>";
});
html += "</ul>";
document.getElementById("userList").innerHTML = html;
}
</script>
服务端 PHP + MySQL
<?php
header('Content-Type: application/json; charset=utf-8');
$city = $_GET['city'] ?? '';
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4',
'root', 'password');
// 使用预处理语句防止 SQL 注入
$stmt = $pdo->prepare('SELECT name, phone FROM users WHERE city = ?');
$stmt->execute([$city]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($users, JSON_UNESCAPED_UNICODE);
?>
安全要点
| 风险 | 防范措施 |
|---|---|
| SQL 注入 | 必须使用预处理语句 / 参数化查询 |
| 敏感数据泄露 | 接口只返回必要的字段 |
| 接口被恶意调用 | 添加身份校验(Token / Session) |
| 数据库压力 | 加缓存、限制查询频率 |
不要把数据库账号密码写死在页面 JS 里——数据库只能由服务端连接,前端永远通过 API 间接访问。
AJAX XML
除了 JSON,AJAX 也可以传输 XML 数据。虽然 XML 如今不如 JSON 流行,但在某些遗留系统、SOAP 服务、配置文件中仍然常见。
获取 XML 并解析
<script>
var xhr = new XMLHttpRequest();
xhr.open("GET", "books.xml", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// responseXML 返回 XML 文档对象
var doc = xhr.responseXML;
var titles = doc.getElementsByTagName("title");
var output = "";
for (var i = 0; i < titles.length; i++) {
output += titles[i].childNodes[0].nodeValue + "<br>";
}
document.getElementById("result").innerHTML = output;
}
};
xhr.send();
</script>
示例 XML 文件 books.xml
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book>
<title>JavaScript 高级程序设计</title>
<author>Nicholas Zakas</author>
<price>99</price>
</book>
<book>
<title>CSS 权威指南</title>
<author>Eric Meyer</author>
<price>129</price>
</book>
</books>
遍历 XML 节点
var books = doc.getElementsByTagName("book");
for (var i = 0; i < books.length; i++) {
var title = books[i].getElementsByTagName("title")[0].childNodes[0].nodeValue;
var price = books[i].getElementsByTagName("price")[0].childNodes[0].nodeValue;
console.log(title + ":" + price + " 元");
}
XML 与 JSON 对比
| 对比项 | XML | JSON |
|---|---|---|
| 解析复杂度 | 需 DOM/XPath | JSON.parse() 一步完成 |
| 体积 | 较大(标签冗余) | 紧凑 |
| 可读性 | 标签语义化强 | 结构直观 |
| 类型支持 | 纯文本 | 原生支持数字/布尔/数组 |
| 适用场景 | 文档型数据、SOAP | Web API 主流选择 |
若服务端返回的是 XML,需确保响应头 Content-Type: text/xml,否则 responseXML 会为 null。
AJAX JSON 实例
现代 AJAX 应用几乎都以 JSON 作为数据格式。本节演示一个完整实例:前端请求 JSON 接口,解析后渲染成 HTML 列表。
HTML 结构
<!DOCTYPE html>
<html>
<body>
<h2>用户列表(AJAX + JSON)</h2>
<button>加载数据</button>
<table border="1" id="userTable">
<tr><th>ID</th><th>姓名</th><th>城市</th></tr>
</table>
<script>
function loadUsers() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "users.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// 1. 解析 JSON 字符串为对象数组
var users = JSON.parse(xhr.responseText);
// 2. 遍历渲染
var table = document.getElementById("userTable");
users.forEach(function(user) {
var row = table.insertRow(-1);
row.insertCell(0).innerHTML = user.id;
row.insertCell(1).innerHTML = user.name;
row.insertCell(2).innerHTML = user.city;
});
}
};
xhr.send();
}
</script>
</body>
</html>
数据文件 users.json
[
{ "id": 1, "name": "张三", "city": "北京" },
{ "id": 2, "name": "李四", "city": "上海" },
{ "id": 3, "name": "王五", "city": "广州" }
]
用现代 fetch 实现同样功能
async function loadUsers() {
const res = await fetch("users.json");
const users = await res.json();
const table = document.getElementById("userTable");
users.forEach(function(user) {
const row = table.insertRow(-1);
row.insertCell(0).innerHTML = user.id;
row.insertCell(1).innerHTML = user.name;
row.insertCell(2).innerHTML = user.city;
});
}
要点回顾
JSON.parse():把 JSON 字符串转成 JavaScript 对象/数组JSON.stringify():把 JavaScript 对象转成 JSON 字符串- 渲染逻辑与数据获取逻辑分离,代码更易维护
JSON.parse 遇到格式错误的 JSON 会抛出异常,生产环境应包在 try/catch 中。
Fetch API
Fetch API 是现代浏览器提供的原生异步请求接口,基于 Promise,语法比 XHR 更简洁、可读性更强,是当前推荐的主流方案。
基本用法
fetch("https://api.example.com/users")
.then(function(response) {
if (!response.ok) {
throw new Error("请求失败:" + response.status);
}
return response.json(); // 解析 JSON
})
.then(function(data) {
console.log(data); // 拿到数据
})
.catch(function(error) {
console.error("出错:", error);
});
与 XHR 对比
| 对比项 | XHR | Fetch |
|---|---|---|
| 语法 | 回调 + 状态判断 | Promise 链式/async-await |
| 代码量 | 繁琐 | 简洁 |
| 流式读取 | 支持(onprogress) | 支持(ReadableStream) |
| 请求中止 | abort() | AbortController |
| 超时控制 | timeout 属性 | 需配合 AbortController |
| 兼容性 | 极老浏览器 | 现代浏览器(IE 不支持) |
async/await 写法(推荐)
async function getUsers() {
try {
const response = await fetch("/api/users");
if (!response.ok) throw new Error("HTTP " + response.status);
const users = await response.json();
return users;
} catch (err) {
console.error(err);
return [];
}
}
getUsers().then(users => console.log(users));
POST 请求
async function createUser() {
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "赵六", age: 28 })
});
const data = await res.json();
console.log(data);
}
注意 fetch 只有在网络错误时才 reject;HTTP 4xx/5xx 状态不会抛异常,必须自己检查 response.ok 或 response.status。
fetch API
本节补充 Fetch API 的进阶能力:请求配置、超时控制、取消请求、表单提交与文件上传等实战技巧。
完整请求配置
const res = await fetch(url, {
method: "GET", // 请求方法
headers: { // 请求头
"Content-Type": "application/json",
"Authorization": "Bearer " + token
},
body: null, // 请求体(GET 不传)
credentials: "same-origin", // 携带 Cookie:"include" 跨域也带
cache: "no-cache", // 缓存策略
redirect: "follow" // 重定向策略
});
超时与取消(AbortController)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch("/api/slow", { signal: controller.signal });
const data = await res.json();
console.log(data);
} catch (err) {
if (err.name === "AbortError") {
console.error("请求超时已取消");
} else {
console.error("其他错误", err);
}
} finally {
clearTimeout(timeoutId);
}
提交表单数据
const form = document.getElementById("myForm");
const formData = new FormData(form);
const res = await fetch("/api/submit", {
method: "POST",
body: formData // 无需手动设置 Content-Type,浏览器自动处理
});
console.log(await res.json());
上传文件
const fileInput = document.getElementById("file");
const file = fileInput.files[0];
const formData = new FormData();
formData.append("file", file);
const res = await fetch("/api/upload", {
method: "POST",
body: formData
});
if (res.ok) {
console.log("上传成功");
}
并发请求
// 并行发起多个请求
const [users, posts] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]);
console.log(users, posts);
跨域请求(CORS)时,credentials: "include" 要求服务器响应头必须包含 Access-Control-Allow-Credentials: true,且不能使用通配符 *。