Ajax 通俗易懂学习手册
目录
- [Ajax 基础概念](#Ajax 基础概念)
- [XMLHttpRequest 详解](#XMLHttpRequest 详解)
- [Fetch API (现代方式)](#Fetch API (现代方式))
- 处理不同数据格式
- 错误处理和状态码
- [Ajax 高级技巧](#Ajax 高级技巧)
- 实战项目案例
- 最佳实践
Ajax 基础概念
什么是 Ajax?
Ajax = A synchronous J avaScript A nd XML
通俗解释: Ajax 就像"外卖小哥",你在网页上点了个按钮(下单),Ajax 悄悄跑到服务器那里取数据(送餐),拿回来后更新页面(送到你手上),整个过程你不用刷新页面!
Ajax 的优势
- ✅ 无需刷新页面 - 用户体验更好
- ✅ 节省带宽 - 只传输需要的数据
- ✅ 提高性能 - 减少服务器负担
- ✅ 实时交互 - 即时获取最新数据
Ajax 能做什么?
javascript
// 常见应用场景
- 搜索建议(输入时实时显示)
- 无限滚动(微博、朋友圈)
- 表单验证(检查用户名是否存在)
- 购物车更新(不刷新页面添加商品)
- 聊天应用(实时收发消息)
- 天气查询(获取实时天气)
XMLHttpRequest 详解
基础用法 - 一步步学会
javascript
// 第1步:创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
// 第2步:配置请求
xhr.open('GET', 'https://api.example.com/users', true);
// 参数说明:
// - 'GET': 请求方法
// - URL: 请求地址
// - true: 是否异步(几乎总是true)
// 第3步:设置响应处理
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log('请求成功!');
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
// 第4步:发送请求
xhr.send();
理解 readyState(请求状态)
javascript
// readyState 的5种状态
0: UNSENT - 请求未初始化
1: OPENED - 连接已建立
2: HEADERS_RECEIVED - 请求已接收
3: LOADING - 请求处理中
4: DONE - 请求已完成
// 实际使用中的状态检查
xhr.onreadystatechange = function() {
console.log('当前状态:', xhr.readyState);
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('成功!', xhr.responseText);
} else {
console.log('出错了,状态码:', xhr.status);
}
}
};
GET 请求完整示例
javascript
function getData(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
// 设置请求头(可选)
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
callback(null, data); // 成功
} else {
callback(new Error(`请求失败: ${xhr.status}`)); // 失败
}
}
};
xhr.send();
}
// 使用示例
getData('https://jsonplaceholder.typicode.com/posts', (error, data) => {
if (error) {
console.error('出错了:', error.message);
} else {
console.log('获取到的数据:', data);
}
});
POST 请求 - 发送数据
javascript
function postData(url, data, callback) {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
// POST 请求必须设置这个头部
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 201) {
const response = JSON.parse(xhr.responseText);
callback(null, response);
} else {
callback(new Error(`请求失败: ${xhr.status}`));
}
}
};
// 发送 JSON 数据
xhr.send(JSON.stringify(data));
}
// 使用示例:创建新用户
const newUser = {
name: '小明',
email: '[email protected]',
age: 25
};
postData('https://jsonplaceholder.typicode.com/users', newUser, (error, response) => {
if (error) {
console.error('创建用户失败:', error.message);
} else {
console.log('创建成功:', response);
}
});
封装成通用函数
javascript
function ajax(options) {
// 默认设置
const defaults = {
method: 'GET',
url: '',
data: null,
headers: {},
timeout: 5000,
success: function() {},
error: function() {}
};
// 合并配置
const config = { ...defaults, ...options };
const xhr = new XMLHttpRequest();
// 设置超时
xhr.timeout = config.timeout;
xhr.open(config.method, config.url, true);
// 设置请求头
for (let key in config.headers) {
xhr.setRequestHeader(key, config.headers[key]);
}
// 如果是 POST 请求且有数据,设置默认 Content-Type
if (config.method === 'POST' && config.data && !config.headers['Content-Type']) {
xhr.setRequestHeader('Content-Type', 'application/json');
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
let response = xhr.responseText;
try {
response = JSON.parse(response);
} catch (e) {
// 如果不是 JSON,保持原样
}
config.success(response);
} else {
config.error(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
}
};
xhr.ontimeout = function() {
config.error(new Error('请求超时'));
};
// 发送数据
let sendData = config.data;
if (sendData && typeof sendData === 'object') {
sendData = JSON.stringify(sendData);
}
xhr.send(sendData);
}
// 使用示例
ajax({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/1',
success: function(data) {
console.log('获取成功:', data);
},
error: function(error) {
console.error('请求失败:', error.message);
}
});
Fetch API (现代方式)
为什么用 Fetch?
Fetch 是现代浏览器提供的新 API,比 XMLHttpRequest 更简洁、更强大!
优势:
- ✅ 基于 Promise,支持 async/await
- ✅ 语法更简洁
- ✅ 更好的错误处理
- ✅ 支持流式数据
基础 GET 请求
javascript
// 基础用法
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('出错了:', error));
// 使用 async/await(推荐)
async function fetchData() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('出错了:', error);
}
}
fetchData();
POST 请求
javascript
async function createPost(postData) {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('创建成功:', result);
return result;
} catch (error) {
console.error('创建失败:', error);
}
}
// 使用示例
createPost({
title: '我的第一篇博客',
body: '这是内容...',
userId: 1
});
常用的 HTTP 方法
javascript
class ApiService {
constructor(baseURL) {
this.baseURL = baseURL;
}
// GET - 获取数据
async get(endpoint) {
const response = await fetch(`${this.baseURL}${endpoint}`);
return this.handleResponse(response);
}
// POST - 创建数据
async post(endpoint, data) {
const response = await fetch(`${this.baseURL}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return this.handleResponse(response);
}
// PUT - 更新数据
async put(endpoint, data) {
const response = await fetch(`${this.baseURL}${endpoint}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return this.handleResponse(response);
}
// DELETE - 删除数据
async delete(endpoint) {
const response = await fetch(`${this.baseURL}${endpoint}`, {
method: 'DELETE'
});
return this.handleResponse(response);
}
// 统一处理响应
async handleResponse(response) {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 如果没有内容,返回 null
if (response.status === 204) {
return null;
}
return await response.json();
}
}
// 使用示例
const api = new ApiService('https://jsonplaceholder.typicode.com');
async function example() {
try {
// 获取所有文章
const posts = await api.get('/posts');
console.log('所有文章:', posts);
// 创建新文章
const newPost = await api.post('/posts', {
title: '新文章',
body: '文章内容',
userId: 1
});
console.log('新文章:', newPost);
// 更新文章
const updatedPost = await api.put('/posts/1', {
id: 1,
title: '更新的标题',
body: '更新的内容',
userId: 1
});
console.log('更新后:', updatedPost);
// 删除文章
await api.delete('/posts/1');
console.log('删除成功');
} catch (error) {
console.error('操作失败:', error.message);
}
}
处理不同数据格式
JSON 数据(最常用)
javascript
// 发送 JSON
async function sendJSON(data) {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return await response.json();
}
// 接收 JSON
async function receiveJSON() {
const response = await fetch('/api/users');
const data = await response.json();
return data;
}
表单数据
javascript
// 发送表单数据
async function sendFormData(formElement) {
const formData = new FormData(formElement);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData // 注意:不要设置 Content-Type,让浏览器自动设置
});
return await response.json();
}
// 手动创建表单数据
async function sendCustomFormData() {
const formData = new FormData();
formData.append('name', '小明');
formData.append('age', '25');
formData.append('avatar', fileInput.files[0]); // 文件
const response = await fetch('/api/profile', {
method: 'POST',
body: formData
});
return await response.json();
}
文件上传
javascript
// 单文件上传
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('上传失败');
}
const result = await response.json();
console.log('上传成功:', result);
return result;
} catch (error) {
console.error('上传出错:', error);
}
}
// 多文件上传
async function uploadMultipleFiles(files) {
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('files', files[i]);
}
const response = await fetch('/api/upload-multiple', {
method: 'POST',
body: formData
});
return await response.json();
}
// 带进度的文件上传(使用 XMLHttpRequest)
function uploadWithProgress(file, onProgress) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
// 上传进度
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
onProgress(percentComplete);
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error('上传失败'));
}
});
xhr.addEventListener('error', () => {
reject(new Error('网络错误'));
});
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
}
// 使用示例
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) {
try {
const result = await uploadWithProgress(file, (progress) => {
console.log(`上传进度:${progress.toFixed(1)}%`);
});
console.log('上传完成:', result);
} catch (error) {
console.error('上传失败:', error);
}
}
});
处理其他格式
javascript
// 获取纯文本
async function getText() {
const response = await fetch('/api/readme.txt');
const text = await response.text();
return text;
}
// 获取二进制数据
async function getBinaryData() {
const response = await fetch('/api/image.jpg');
const blob = await response.blob();
return blob;
}
// 获取数组缓冲区
async function getArrayBuffer() {
const response = await fetch('/api/data.bin');
const buffer = await response.arrayBuffer();
return buffer;
}
错误处理和状态码
HTTP 状态码详解
javascript
// 常见状态码及其含义
const statusCodes = {
200: '成功',
201: '创建成功',
204: '删除成功(无内容)',
400: '请求参数错误',
401: '未授权',
403: '禁止访问',
404: '资源不存在',
500: '服务器内部错误',
502: '网关错误',
503: '服务不可用'
};
function getStatusMessage(status) {
return statusCodes[status] || '未知错误';
}
完善的错误处理
javascript
class ApiError extends Error {
constructor(status, message, data = null) {
super(message);
this.name = 'ApiError';
this.status = status;
this.data = data;
}
}
async function safeFetch(url, options = {}) {
try {
const response = await fetch(url, options);
// 检查响应状态
if (!response.ok) {
let errorData = null;
try {
errorData = await response.json();
} catch (e) {
// 如果响应不是 JSON,忽略
}
throw new ApiError(
response.status,
errorData?.message || `HTTP ${response.status}: ${response.statusText}`,
errorData
);
}
// 根据 Content-Type 自动解析
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else if (contentType && contentType.includes('text/')) {
return await response.text();
} else {
return await response.blob();
}
} catch (error) {
if (error instanceof ApiError) {
throw error;
}
// 网络错误
if (error.name === 'TypeError') {
throw new ApiError(0, '网络连接失败,请检查网络设置');
}
// 其他错误
throw new ApiError(0, error.message);
}
}
// 使用示例
async function handleApiCall() {
try {
const data = await safeFetch('/api/users/123');
console.log('获取成功:', data);
} catch (error) {
if (error instanceof ApiError) {
switch (error.status) {
case 401:
console.error('请先登录');
// 跳转到登录页
break;
case 403:
console.error('权限不足');
break;
case 404:
console.error('用户不存在');
break;
case 0:
console.error('网络错误:', error.message);
break;
default:
console.error('请求失败:', error.message);
}
} else {
console.error('未知错误:', error);
}
}
}
重试机制
javascript
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let i = 0; i <= maxRetries; i++) {
try {
const response = await fetch(url, options);
// 如果是服务器错误且还有重试次数,继续重试
if (response.status >= 500 && i < maxRetries) {
throw new Error(`服务器错误 ${response.status}`);
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
lastError = error;
if (i < maxRetries) {
console.log(`第 ${i + 1} 次请求失败,${1}秒后重试...`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); // 指数退避
}
}
}
throw lastError;
}
// 使用示例
async function robustApiCall() {
try {
const data = await fetchWithRetry('/api/unstable-endpoint');
console.log('最终成功:', data);
} catch (error) {
console.error('重试后仍然失败:', error.message);
}
}
Ajax 高级技巧
取消请求
javascript
// 使用 AbortController 取消请求
function cancelableRequest(url) {
const controller = new AbortController();
const promise = fetch(url, {
signal: controller.signal
}).then(response => response.json());
// 返回 promise 和取消函数
return {
promise,
cancel: () => controller.abort()
};
}
// 使用示例
const { promise, cancel } = cancelableRequest('/api/slow-endpoint');
// 5秒后自动取消
setTimeout(() => {
cancel();
console.log('请求已取消');
}, 5000);
promise
.then(data => console.log('获取成功:', data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求被取消了');
} else {
console.error('请求失败:', error);
}
});
请求缓存
javascript
class ApiCache {
constructor(expireTime = 5 * 60 * 1000) { // 默认5分钟过期
this.cache = new Map();
this.expireTime = expireTime;
}
// 生成缓存键
getCacheKey(url, options = {}) {
return JSON.stringify({ url, ...options });
}
// 获取缓存
get(key) {
const item = this.cache.get(key);
if (!item) return null;
// 检查是否过期
if (Date.now() > item.expireAt) {
this.cache.delete(key);
return null;
}
return item.data;
}
// 设置缓存
set(key, data) {
this.cache.set(key, {
data,
expireAt: Date.now() + this.expireTime
});
}
// 带缓存的请求
async fetch(url, options = {}) {
const cacheKey = this.getCacheKey(url, options);
// 先查缓存
const cached = this.get(cacheKey);
if (cached) {
console.log('使用缓存数据');
return cached;
}
// 发起请求
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
// 缓存结果
this.set(cacheKey, data);
return data;
}
// 清除缓存
clear() {
this.cache.clear();
}
}
// 使用示例
const apiCache = new ApiCache();
async function getCachedData() {
try {
const data = await apiCache.fetch('/api/users');
console.log('数据:', data);
} catch (error) {
console.error('获取失败:', error);
}
}
// 第一次调用会发起请求
getCachedData();
// 第二次调用会使用缓存
setTimeout(() => {
getCachedData(); // 使用缓存
}, 1000);
并发控制
javascript
class RequestQueue {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({
requestFn,
resolve,
reject
});
this.process();
});
}
async process() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.running++;
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process(); // 处理下一个请求
}
}
}
// 使用示例
const requestQueue = new RequestQueue(2); // 最多同时2个请求
async function batchRequests() {
const urls = [
'/api/user/1',
'/api/user/2',
'/api/user/3',
'/api/user/4',
'/api/user/5'
];
const promises = urls.map(url =>
requestQueue.add(() => fetch(url).then(r => r.json()))
);
try {
const results = await Promise.all(promises);
console.log('所有请求完成:', results);
} catch (error) {
console.error('批量请求失败:', error);
}
}
batchRequests();
实战项目案例
用户管理系统
javascript
class UserManager {
constructor() {
this.baseURL = '/api/users';
this.users = [];
this.init();
}
async init() {
await this.loadUsers();
this.bindEvents();
}
// 加载用户列表
async loadUsers() {
try {
const response = await fetch(this.baseURL);
this.users = await response.json();
this.renderUsers();
} catch (error) {
this.showError('加载用户失败:' + error.message);
}
}
// 创建用户
async createUser(userData) {
try {
const response = await fetch(this.baseURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error('创建失败');
}
const newUser = await response.json();
this.users.push(newUser);
this.renderUsers();
this.showSuccess('用户创建成功!');
} catch (error) {
this.showError('创建用户失败:' + error.message);
}
}
// 更新用户
async updateUser(id, userData) {
try {
const response = await fetch(`${this.baseURL}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error('更新失败');
}
const updatedUser = await response.json();
const index = this.users.findIndex(u => u.id === id);
if (index !== -1) {
this.users[index] = updatedUser;
this.renderUsers();
this.showSuccess('用户更新成功!');
}
} catch (error) {
this.showError('更新用户失败:' + error.message);
}
}
// 删除用户
async deleteUser(id) {
if (!confirm('确定要删除这个用户吗?')) {
return;
}
try {
const response = await fetch(`${this.baseURL}/${id}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('删除失败');
}
this.users = this.users.filter(u => u.id !== id);
this.renderUsers();
this.showSuccess('用户删除成功!');
} catch (error) {
this.showError('删除用户失败:' + error.message);
}
}
// 渲染用户列表
renderUsers() {
const container = document.getElementById('userList');
container.innerHTML = this.users.map(user => `
<div class="user-item" data-id="${user.id}">
<h3>${user.name}</h3>
<p>邮箱:${user.email}</p>
<p>年龄:${user.age}</p>
<button onclick="userManager.editUser(${user.id})">编辑</button>
<button onclick="userManager.deleteUser(${user.id})">删除</button>
</div>
`).join('');
}
// 绑定事件
bindEvents() {
// 创建用户表单
document.getElementById('createForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const userData = {
name: formData.get('name'),
email: formData.get('email'),
age: parseInt(formData.get('age'))
};
await this.createUser(userData);
e.target.reset();
});
}
// 编辑用户
editUser(id) {
const user = this.users.find(u => u.id === id);
if (user) {
document.getElementById('editName').value = user.name;
document.getElementById('editEmail').value = user.email;
document.getElementById('editAge').value = user.age;
document.getElementById('editModal').style.display = 'block';
document.getElementById('editForm').onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const userData = {
name: formData.get('name'),
email: formData.get('email'),
age: parseInt(formData.get('age'))
};
await this.updateUser(id, userData);
document.getElementById('editModal').style.display = 'none';
};
}
}
// 显示成功消息
showSuccess(message) {
this.showMessage(message, 'success');
}
// 显示错误消息
showError(message) {
this.showMessage(message, 'error');
}
// 显示消息
showMessage(message, type) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
messageDiv.textContent = message;
document.body.appendChild(messageDiv);
setTimeout(() => {
messageDiv.remove();
}, 3000);
}
}
// 初始化
const userManager = new UserManager();
搜索建议功能
javascript
class SearchSuggestion {
constructor(inputId, suggestionsId) {
this.input = document.getElementById(inputId);
this.suggestionsContainer = document.getElementById(suggestionsId);
this.cache = new Map();
this.currentRequest = null;
this.debounceTimer = null;
this.init();
}
init() {
this.input.addEventListener('input', (e) => {
this.debounceSearch(e.target.value);
});
this.input.addEventListener('keydown', (e) => {
this.handleKeyboard(e);
});
// 点击外部关闭建议
document.addEventListener('click', (e) => {
if (!this.input.contains(e.target) && !this.suggestionsContainer.contains(e.target)) {
this.hideSuggestions();
}
});
}
// 防抖搜索
debounceSearch(query) {
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
this.search(query);
}, 300);
}
async search(query) {
if (!query.trim()) {
this.hideSuggestions();
return;
}
// 取消之前的请求
if (this.currentRequest) {
this.currentRequest.abort();
}
// 检查缓存
if (this.cache.has(query)) {
this.showSuggestions(this.cache.get(query));
return;
}
try {
const controller = new AbortController();
this.currentRequest = controller;
const response = await fetch(`/api/search/suggestions?q=${encodeURIComponent(query)}`, {
signal: controller.signal
});
if (!response.ok) {
throw new Error('搜索失败');
}
const suggestions = await response.json();
// 缓存结果
this.cache.set(query, suggestions);
this.showSuggestions(suggestions);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('搜索出错:', error);
}
} finally {
this.currentRequest = null;
}
}
showSuggestions(suggestions) {
if (suggestions.length === 0) {
this.hideSuggestions();
return;
}
const html = suggestions.map((item, index) => `
<div class="suggestion-item" data-index="${index}" onclick="searchSuggestion.selectSuggestion('${item.text}')">
<span class="suggestion-text">${this.highlightQuery(item.text)}</span>
<span class="suggestion-count">${item.count} 个结果</span>
</div>
`).join('');
this.suggestionsContainer.innerHTML = html;
this.suggestionsContainer.style.display = 'block';
}
hideSuggestions() {
this.suggestionsContainer.style.display = 'none';
}
selectSuggestion(text) {
this.input.value = text;
this.hideSuggestions();
this.performSearch(text);
}
highlightQuery(text) {
const query = this.input.value.trim();
if (!query) return text;
const regex = new RegExp(`(${query})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
handleKeyboard(e) {
const items = this.suggestionsContainer.querySelectorAll('.suggestion-item');
const current = this.suggestionsContainer.querySelector('.suggestion-item.active');
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
if (current) {
current.classList.remove('active');
const next = current.nextElementSibling || items[0];
next.classList.add('active');
} else if (items.length > 0) {
items[0].classList.add('active');
}
break;
case 'ArrowUp':
e.preventDefault();
if (current) {
current.classList.remove('active');
const prev = current.previousElementSibling || items[items.length - 1];
prev.classList.add('active');
} else if (items.length > 0) {
items[items.length - 1].classList.add('active');
}
break;
case 'Enter':
e.preventDefault();
if (current) {
const text = current.querySelector('.suggestion-text').textContent;
this.selectSuggestion(text);
} else {
this.performSearch(this.input.value);
}
break;
case 'Escape':
this.hideSuggestions();
break;
}
}
performSearch(query) {
console.log('执行搜索:', query);
// 这里实现实际的搜索逻辑
}
}
// 初始化搜索建议
const searchSuggestion = new SearchSuggestion('searchInput', 'suggestions');
最佳实践
1. 安全性考虑
javascript
// CSRF 防护
function getCSRFToken() {
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
}
// 带 CSRF 令牌的请求
async function secureRequest(url, options = {}) {
const defaultHeaders = {
'X-CSRF-TOKEN': getCSRFToken(),
'Content-Type': 'application/json'
};
return fetch(url, {
...options,
headers: {
...defaultHeaders,
...options.headers
}
});
}
// 验证响应数据
function validateResponse(data, schema) {
// 简单的数据验证示例
for (let key in schema) {
if (schema[key].required && !data.hasOwnProperty(key)) {
throw new Error(`缺少必需字段:${key}`);
}
if (data[key] && schema[key].type && typeof data[key] !== schema[key].type) {
throw new Error(`字段类型错误:${key}`);
}
}
return true;
}
2. 性能优化
javascript
// 请求去重
class RequestDeduplicator {
constructor() {
this.pendingRequests = new Map();
}
async request(key, requestFn) {
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}
const promise = requestFn().finally(() => {
this.pendingRequests.delete(key);
});
this.pendingRequests.set(key, promise);
return promise;
}
}
const deduplicator = new RequestDeduplicator();
// 使用示例
async function getUser(id) {
return deduplicator.request(`user-${id}`, () =>
fetch(`/api/users/${id}`).then(r => r.json())
);
}
// 多次调用只会发起一次请求
getUser(1);
getUser(1);
getUser(1);
3. 错误监控
javascript
// 全局错误监控
class ErrorMonitor {
constructor() {
this.errors = [];
this.maxErrors = 100;
}
log(error, context = {}) {
const errorInfo = {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent,
context
};
this.errors.push(errorInfo);
// 保持错误日志数量
if (this.errors.length > this.maxErrors) {
this.errors.shift();
}
// 上报错误(可选)
this.reportError(errorInfo);
}
async reportError(errorInfo) {
try {
await fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorInfo)
});
} catch (e) {
console.error('错误上报失败:', e);
}
}
getErrors() {
return this.errors;
}
}
const errorMonitor = new ErrorMonitor();
// 包装 fetch 以监控错误
async function monitoredFetch(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
errorMonitor.log(error, { url, options, status: response.status });
throw error;
}
return response;
} catch (error) {
errorMonitor.log(error, { url, options });
throw error;
}
}
4. 通用工具函数
javascript
// Ajax 工具库
const AjaxUtils = {
// 构建查询字符串
buildQuery(params) {
return new URLSearchParams(params).toString();
},
// 解析响应头
parseHeaders(response) {
const headers = {};
for (let [key, value] of response.headers.entries()) {
headers[key] = value;
}
return headers;
},
// 检查网络状态
isOnline() {
return navigator.onLine;
},
// 等待网络恢复
waitForOnline() {
return new Promise(resolve => {
if (this.isOnline()) {
resolve();
} else {
const handler = () => {
if (this.isOnline()) {
window.removeEventListener('online', handler);
resolve();
}
};
window.addEventListener('online', handler);
}
});
},
// 格式化文件大小
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
};
总结
Ajax 学习路径
- 掌握基础 - XMLHttpRequest 和 Fetch API
- 理解概念 - 同步、异步、状态码、错误处理
- 实践应用 - 表单提交、文件上传、数据获取
- 高级技巧 - 缓存、重试、并发控制、请求取消
- 项目实战 - 结合实际业务场景
开发建议
- ✅ 优先使用 Fetch API,语法更简洁
- ✅ 总是处理错误,提供友好的用户体验
- ✅ 使用 async/await,代码更易读
- ✅ 实现加载状态,让用户知道正在处理
- ✅ 适当使用缓存,提升性能
- ✅ 考虑网络状况,处理离线场景
调试技巧
javascript
// 在浏览器控制台查看网络请求
// 1. 打开开发者工具 (F12)
// 2. 切换到 Network 选项卡
// 3. 重新发起请求,查看详细信息
// 使用 console.log 调试
fetch('/api/data')
.then(response => {
console.log('响应状态:', response.status);
console.log('响应头:', response.headers);
return response.json();
})
.then(data => {
console.log('响应数据:', data);
});
记住:Ajax 是现代 Web 开发的核心技术,多练习、多实战才能真正掌握! 🚀