Ajax(Asynchronous JavaScript and XML)是 Web 2.0 时代的核心技术。它让 JS 可以在不刷新页面的情况下,主动向服务器发起 HTTP 请求、获取数据、动态更新页面。
没有 Ajax 之前,任何数据更新都要整页刷新 ------ 体验极差。Ajax 让 "单页应用(SPA)" 成为可能。
1. 数据序列化:JSON.stringify
网络传输的是二进制文本,JS 对象不能直接发送,需要序列化为 JSON 字符串。
js
// 服务端:把 JS 对象序列化成 JSON 字符串,发给客户端
const todo = [
{ id: 1, title: '过四六级', completed: false },
{ id: 2, title: '回家过节', completed: false }
];
// JSON.stringify(value, replace?, space?)
// replace: 过滤/替换,传 null 表示原样序列化
// space: 缩进空格数,团队规范,提升可读性
const jsonStr = JSON.stringify(todo, null, 2);
// 结果:格式化的 JSON 字符串,可读性好
三个参数:
| 参数 | 说明 |
|---|---|
value |
要序列化的 JS 对象 |
replace? |
过滤/替换函数或数组,null 表示原样输出 |
space? |
缩进空格数,推荐 2,团队规范,可读性好 |
客户端收到后,用 JSON.parse() 还原:
js
const todo = JSON.parse(xhr.responseText); // JSON 字符串 → JS 对象
2. JS 异步处理
JS 是单线程 语言 ------ 同一时间只能做一件事。遇到耗时操作(网络请求、定时器、文件读写),不能卡在那里等,必须用异步机制来处理。
2.1 事件循环(Event Loop)
同步代码 → 全部执行完
异步任务 → 放入 Event Loop 队列
等同步代码跑完 → 从 Event Loop 里拿出回调函数执行
js
console.log('1. 开始');
setTimeout(() => {
console.log('3. 定时器回调'); // 异步,进入 Event Loop
}, 0);
console.log('2. 结束');
// 输出顺序:1 → 2 → 3
// 即使 setTimeout 设了 0ms,也要等同步代码全部执行完
2.2 三种异步写法
JS 异步处理经历了三代演进:
| 方式 | 年代 | 特点 |
|---|---|---|
| 回调函数(Callback) | 古老 | 嵌套深了变成"回调地狱",难以维护 |
| Promise + then() | ES6 | 链式调用,解决了回调地狱 |
| async / await | ES2017 | 最推荐,写法跟同步代码一样,可读性极佳 |
js
// 方式一:回调函数 --- 最原始
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// 拿到数据后的处理...
}
};
// 方式二:Promise + then() --- 好一些
fetch('/todo')
.then(res => res.json())
.then(data => {
// 拿到数据后的处理...
})
.catch(err => console.error(err));
// 方式三:async / await --- 最推荐!
const response = await fetch('/todo');
const data = await response.json();
// 跟同步代码看起来一模一样,清晰直观
3. 后端:Node.js HTTP 服务器
用 Node.js 内置的 http 模块创建一个简单的 API 服务器。
js
// backend/index.js
// node 内置的 http 模块,用于创建 http 服务器
// require 是 Node.js 早期的模块化系统 CommonJS
// EMS 是升级版:import + export default
const http = require('http');
// 伺服状态 --- http 基于请求-响应模型
http.createServer((req, res) => {
const todo = [
{ id: 1, title: '过四六级', completed: false },
{ id: 2, title: '回家过节', completed: false }
];
// 设置 CORS 响应头,允许跨域访问
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/json;charset=utf-8');
// req.url --- 用户请求的路径
if (req.url === '/') {
res.end('hello world');
}
if (req.url === '/todo') {
// 序列化为 JSON 字符串后返回
res.end(JSON.stringify(todo));
}
}).listen(3000, () => {
console.log('server is running on 3000 port');
});
启动:
bash
node backend/index.js
# 输出: server is running on 3000 port
4. 前端:从 XHR 到 Fetch
4.1 XMLHttpRequest(经典方式)
XMLHttpRequest(XHR)是 Ajax 的底层实现,fetch 的"前辈"。
html
<!-- frontend/index.html -->
<body>
<ul id="todo"></ul>
<button id="btn">获取todo</button>
<script>
document.getElementById('btn')
.addEventListener('click', () => {
// 1. 实例化 XHR 对象
const xhr = new XMLHttpRequest();
// 2. 打开 HTTP 通道(第三个参数 true = 异步)
xhr.open('GET', 'http://localhost:3000/todo', true);
// 3. 监听响应(回调函数 callback)
xhr.onreadystatechange = function () {
// readyState: 0→1→2→3→4
if (xhr.readyState === 4 && xhr.status === 200) {
const todo = JSON.parse(xhr.responseText);
document.getElementById('todo')
.innerHTML = todo
.map(item => `<li>${item.title}</li>`)
.join('');
}
};
// 4. 发送请求
xhr.send();
});
</script>
</body>
XHR readyState 状态码:
| readyState | 含义 |
|---|---|
| 0 | UNSENT --- 还没调用 open |
| 1 | OPENED --- 已调用 open |
| 2 | HEADERS_RECEIVED --- 已收到响应头 |
| 3 | LOADING --- 正在接收响应体 |
| 4 | DONE --- 请求完成 |
4.2 Fetch + async/await(现代方式)
fetch 是 XHR 的现代替代品,基于 Promise,配合 async/await 使用体验极佳。
html
<!-- frontend/fetch.html -->
<body>
<ul id="todo"></ul>
<button id="btn">获取todo(Fetch版)</button>
<script>
document.getElementById('btn')
.addEventListener('click', async () => {
try {
// fetch 返回 Promise,await 等它完成
const response = await fetch('http://localhost:3000/todo');
const todos = await response.json();
document.getElementById('todo')
.innerHTML = todos
.map(todo => `<li>${todo.title}</li>`)
.join('');
} catch (error) {
console.error('请求失败:', error);
}
});
</script>
</body>
对比 XHR:fetch 代码量少了近一半,逻辑更线性、更易读,错误处理也更优雅。
5. 技术演进路线
XHR(回调地狱)
↓
fetch + Promise.then(链式调用)
↓
fetch + async/await(推荐,像同步代码一样写异步逻辑)
核心要点 :
async/await是目前最优秀的异步处理方式 --- 看起来和同步代码一样直观,但底层依然是异步、非阻塞的。
6. 文件结构
ajax/
├── readme.md ← 本文
├── backend/
│ ├── package.json
│ └── index.js ← Node.js HTTP 服务器
└── frontend/
├── index.html ← XHR 经典版
└── fetch.html ← Fetch + async/await 现代版