在前端开发的发展历程中,AJAX 无疑是具有里程碑意义的技术突破。它彻底打破了传统 Web 应用 “请求 – 刷新 – 重载” 的固有模式,让页面实现流畅的局部无刷新数据交互,为单页应用(SPA)、实时聊天、动态筛选等现代前端功能奠定了坚实基础。本文将从核心原理出发,逐步拆解 AJAX 的使用方法,结合实战案例帮你吃透这项前端必备技能。
一、什么是 AJAX?先理清核心概念
1. AJAX 的全称与本质
AJAX 是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)的缩写,但这一命名带有明显的时代印记 —— 如今 XML 已几乎被 JSON 取代,而 AJAX 的本质也并非单一技术,而是一套技术组合方案:以 JavaScript 为核心,结合网络请求 API、DOM 操作、数据格式解析,实现客户端与服务器的异步数据通信。
2. 核心特性:异步交互
这是 AJAX 最核心的价值所在,我们先对比传统 Web 交互与 AJAX 交互的差异:
- 传统 Web 交互:用户触发操作(如提交表单)→ 浏览器发送完整请求 → 服务器返回全新页面 → 浏览器重新加载整个页面。这个过程中页面会出现空白卡顿,用户体验差,且大量重复资源被重新加载。
- AJAX 异步交互:用户触发操作 → JavaScript 悄悄发送局部请求(不阻塞页面)→ 服务器返回仅需的业务数据 → JavaScript 操作 DOM 局部更新页面。整个过程页面不刷新,用户操作无感知,交互更流畅。
生活中的典型案例:百度搜索框输入关键词时的实时联想建议、电商平台的商品筛选(无需刷新页面即可展示筛选结果)、社交媒体的实时消息推送,都是 AJAX 异步交互的体现。
3. AJAX 的技术组成
AJAX 并非凭空存在,它依赖以下四大核心技术支撑:
二、AJAX 核心用法:传统 XHR 与现代 Fetch
实现 AJAX 交互的核心是发送网络请求,目前有两种主流方案:传统的 XMLHttpRequest(简称 XHR)和现代的 Fetch API。前者兼容性极佳(支持 IE 浏览器),后者基于 Promise 设计,写法更简洁优雅,是当前前端开发的主流选择。
1. 传统方案:XMLHttpRequest 完整流程
XMLHttpRequest 是 AJAX 的底层实现,理解它能帮你吃透 AJAX 的核心逻辑。完整的请求流程分为 5 步:创建对象 → 配置请求 → 监听响应 → 发送请求 → 处理结果。
示例 1:发送 GET 请求(获取数据)
GET 请求常用于从服务器获取数据,参数通常通过 URL 拼接传递(?key1=value1&key2=value2)。
javascript
运行
// 1. 创建 XMLHttpRequest 实例
const xhr = new XMLHttpRequest();
// 2. 配置请求:open(请求方式, 接口地址, 是否异步)
// 第三个参数 true = 异步(推荐),false = 同步(会阻塞页面,不推荐)
xhr.open('GET', 'https://jsonplaceholder.typicode.com/users/1', true);
// 3. 监听响应状态变化(核心:判断请求是否成功)
xhr.onreadystatechange = function() {
// readyState:请求状态码,4 表示服务器响应完全接收
// status:HTTP 状态码,200 表示请求成功(2xx 均为成功状态)
if (xhr.readyState === 4 && xhr.status === 200) {
// 4. 处理响应数据:JSON 格式需用 JSON.parse() 解析
const userData = JSON.parse(xhr.responseText);
// 5. 操作 DOM 局部更新页面
renderUserInfo(userData);
} else if (xhr.readyState === 4) {
// 处理请求失败场景(状态码非 2xx)
console.error(`请求失败,HTTP 状态码:${xhr.status}`);
alert('数据获取失败,请稍后重试');
}
};
// 4. 发送请求:GET 请求无请求体,传入 null 即可
xhr.send(null);
// 辅助函数:渲染用户信息到页面
function renderUserInfo(user) {
const userContainer = document.getElementById('user-container');
userContainer.innerHTML = `
<h3>用户信息</h3>
<p>姓名:${user.name}</p>
<p>邮箱:${user.email}</p>
<p>手机号:${user.phone}</p>
<p>公司:${user.company.name}</p>
`;
}
示例 2:发送 POST 请求(提交数据)
POST 请求常用于向服务器提交数据(如注册、新增内容),请求参数放在请求体中,且需要设置请求头告知服务器数据格式。
javascript
运行
// 1. 创建 XHR 实例
const xhr = new XMLHttpRequest();
// 2. 配置 POST 请求
xhr.open('POST', 'https://jsonplaceholder.typicode.com/posts', true);
// 关键:设置请求头,告知服务器请求体为 JSON 格式
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8');
// 3. 监听响应(使用 onload 简化:等价于 readyState === 4 时触发)
xhr.onload = function() {
if (xhr.status === 201) { // 201 表示资源创建成功(POST 请求常用成功状态码)
const newPost = JSON.parse(xhr.responseText);
console.log('文章新增成功:', newPost);
alert(`新增文章 ID:${newPost.id},标题:${newPost.title}`);
} else {
console.error(`提交失败,HTTP 状态码:${xhr.status}`);
alert('文章提交失败,请稍后重试');
}
};
// 4. 准备请求体数据,并转为 JSON 字符串
const postData = {
title: 'AJAX POST 请求学习笔记',
body: '这是一篇通过 XHR POST 请求新增的文章',
userId: 1
};
// 5. 发送请求:传入 JSON 格式的请求体
xhr.send(JSON.stringify(postData));
2. 现代方案:Fetch API (简洁优雅的主流选择)
Fetch API 是 ES6 推出的新一代网络请求方案,基于 Promise 设计,支持链式调用和 async/await 语法,写法更简洁,无需繁琐的状态监听,已逐渐取代 XMLHttpRequest 成为前端主流。
注意:Fetch API 不兼容 IE 浏览器,若需兼容旧浏览器,可通过 whatwg-fetch 垫片库实现兼容。
示例 1:Fetch 发送 GET 请求
javascript
运行
// Fetch 核心:调用 fetch() 方法,传入接口地址,返回 Promise 对象
fetch('https://jsonplaceholder.typicode.com/users/1')
// 第一步:处理响应状态,解析数据
.then(response => {
// response.ok:判断 HTTP 状态码是否在 200-299 之间
if (!response.ok) {
throw new Error(`请求失败:${response.status}`); // 抛出异常,进入 catch 回调
}
// 解析 JSON 数据(替代 JSON.parse(),返回新的 Promise)
return response.json();
})
// 第二步:使用解析后的数据渲染页面
.then(userData => {
renderUserInfo(userData);
})
// 捕获所有异常(网络错误、HTTP 错误)
.catch(error => {
console.error('请求异常:', error);
alert('数据获取失败,请检查网络连接');
});
// 复用之前的 renderUserInfo 函数
function renderUserInfo(user) {
const userContainer = document.getElementById('user-container');
userContainer.innerHTML = `
<h3>用户信息</h3>
<p>姓名:${user.name}</p>
<p>邮箱:${user.email}</p>
<p>手机号:${user.phone}</p>
<p>公司:${user.company.name}</p>
`;
}
示例 2:Fetch 发送 POST 请求
Fetch 发送 POST 请求时,需传入第二个参数(配置对象),指定 method、headers、body 等属性。
javascript
运行
// 准备提交的数据
const postData = {
title: 'Fetch API 学习笔记',
body: '这是一篇通过 Fetch POST 请求新增的文章',
userId: 1
};
// Fetch 配置 POST 请求
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST', // 指定请求方式
headers: {
'Content-Type': 'application/json;charset=utf-8' // 设置请求头
},
body: JSON.stringify(postData) // 请求体数据(转为 JSON 字符串)
})
.then(response => {
if (!response.ok) {
throw new Error(`提交失败:${response.status}`);
}
return response.json();
})
.then(newPost => {
console.log('文章新增成功:', newPost);
alert(`新增文章 ID:${newPost.id},标题:${newPost.title}`);
})
.catch(error => {
console.error('请求异常:', error);
alert('文章提交失败,请检查网络连接');
});
优化:用 async/await 简化 Fetch 代码
async/await 是 ES7 语法,可将 Promise 链式调用转为同步写法,代码更简洁易读,是当前 Fetch 请求的最优写法。
javascript
运行
// 封装为 async 函数
async function addNewPost() {
const postData = {
title: 'Async/Await 优化 Fetch 请求',
body: '使用 async/await 让 Fetch 代码更简洁',
userId: 1
};
try {
// 发送 POST 请求
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(postData)
});
// 判断响应状态
if (!response.ok) {
throw new Error(`提交失败:${response.status}`);
}
// 解析数据
const newPost = await response.json();
console.log('文章新增成功:', newPost);
alert(`新增文章 ID:${newPost.id},标题:${newPost.title}`);
} catch (error) {
console.error('请求异常:', error);
alert('文章提交失败,请检查网络连接');
}
}
// 调用函数
addNewPost();
三、AJAX 实战:实现实时用户名唯一性校验
我们结合实际开发场景,实现 “用户注册时实时校验用户名是否已存在” 的功能 —— 用户输入用户名并失去焦点时,触发 AJAX 请求后台接口,实时返回 “用户名可用” 或 “用户名已被占用” 的提示,全程无页面刷新。
完整可运行代码
html
预览
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>AJAX 实时用户名校验</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.register-container {
width: 400px;
margin: 50px auto;
padding: 30px;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #333;
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
.form-group input:focus {
outline: none;
border-color: #4299e1;
box-shadow: 0 0 0 2px rgba(66,153,225,0.2);
}
.tip-text {
margin-top: 6px;
font-size: 12px;
height: 16px;
}
.success {
color: #48bb78;
}
.error {
color: #f56565;
}
.loading {
color: #90a4ae;
}
</style>
</head>
<body>
<div class="register-container">
<h2 style="text-align: center; margin-bottom: 30px; color: #333;">用户注册</h2>
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" placeholder="请输入3-16位字母/数字组合">
<div class="tip-text" id="usernameTip"></div>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" placeholder="请输入6-20位密码">
</div>
<div class="form-group">
<button style="width: 100%; padding: 10px; background: #4299e1; color: #fff; border: none; border-radius: 4px; cursor: pointer;">
注册
</button>
</div>
</div>
<script>
// 1. 获取 DOM 元素
const usernameInput = document.getElementById('username');
const usernameTip = document.getElementById('usernameTip');
// 2. 绑定失去焦点事件(blur)
usernameInput.addEventListener('blur', async function() {
const username = this.value.trim();
const tipDom = document.getElementById('usernameTip');
// 第一步:本地空值校验
if (!username) {
tipDom.textContent = '用户名不能为空';
tipDom.className = 'tip-text error';
return;
}
// 第二步:本地格式校验
const usernameReg = /^[a-zA-Z0-9]{3,16}$/;
if (!usernameReg.test(username)) {
tipDom.textContent = '请输入3-16位字母/数字组合的用户名';
tipDom.className = 'tip-text error';
return;
}
// 第三步:显示加载状态
tipDom.textContent = '正在校验用户名…';
tipDom.className = 'tip-text loading';
// 第四步:AJAX 异步校验用户名(使用 Fetch + async/await)
try {
// 拼接请求参数:模拟后台校验接口
const response = await fetch(`https://jsonplaceholder.typicode.com/users?username=${username}`);
// 判断响应状态
if (!response.ok) {
throw new Error('网络异常,校验失败');
}
// 解析响应数据
const userList = await response.json();
// 第五步:根据响应结果更新提示
if (userList.length === 0) {
// 用户名未被占用
tipDom.textContent = '用户名可用';
tipDom.className = 'tip-text success';
} else {
// 用户名已被占用
tipDom.textContent = '用户名已被占用,请更换';
tipDom.className = 'tip-text error';
}
} catch (error) {
// 捕获异常,提示错误信息
tipDom.textContent = error.message;
tipDom.className = 'tip-text error';
console.error('用户名校验异常:', error);
}
});
</script>
</body>
</html>
功能说明
四、AJAX 进阶技巧与注意事项
1. 跨域问题解决方案
AJAX 遵循 “同源策略”(协议、域名、端口三者一致为同源),跨域请求会被浏览器拦截。实际开发中常用的解决方案有:
- CORS:后台在响应头中添加 Access-Control-Allow-Origin 等字段,允许指定域名跨域访问(推荐方案)。
- JSONP:利用 <script> 标签不受同源策略限制的特性,仅支持 GET 请求(兼容旧浏览器)。
- 代理服务器:前端请求本地代理服务器,由代理服务器转发请求到目标服务器(前端无需修改,常用于开发环境)。
2. 请求取消
在实际开发中,若用户快速切换操作(如快速输入搜索关键词),可能会产生多个冗余请求,此时需要取消之前未完成的请求:
- XHR:调用 xhr.abort() 方法取消请求。
- Fetch:使用 AbortController 实现请求取消。
3. 请求封装
为避免重复编写请求代码,可将 Fetch 或 XHR 封装为通用请求函数,统一处理请求头、参数、异常等逻辑,例如:
javascript
运行
// 通用 Fetch 请求封装
async function request(url, options = {}) {
// 默认配置
const defaultOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
};
// 合并配置
const finalOptions = { …defaultOptions, …options };
// 处理 GET 请求参数
if (finalOptions.method.toUpperCase() === 'GET' && options.params) {
const paramsStr = new URLSearchParams(options.params).toString();
url = `${url}?${paramsStr}`;
}
// 处理请求体
if (finalOptions.body && typeof finalOptions.body === 'object') {
finalOptions.body = JSON.stringify(finalOptions.body);
}
try {
const response = await fetch(url, finalOptions);
if (!response.ok) {
throw new Error(`请求失败:${response.status}`);
}
return await response.json();
} catch (error) {
console.error('请求异常:', error);
throw error; // 抛出异常,让调用方处理
}
}
// 调用封装后的函数
request('https://jsonplaceholder.typicode.com/users', {
params: { id: 1 }
}).then(data => console.log(data));
4. 加载状态与防抖节流
- 加载状态:发送请求时显示加载动画(如骨架屏、loading 图标),请求完成后隐藏,提升用户体验。
- 防抖节流:对于高频触发的 AJAX 请求(如搜索框输入),使用防抖(debounce)或节流(throttle)函数,减少请求次数,减轻服务器压力。



