Ajax 通俗易懂学习手册目录Ajax 基础概念XMLHttpRequest 详解Fetch API (现代方式)处理不同数据格式错误处理和状态码Ajax 高级技巧实战项目案例最佳实践Ajax 基础概念什么是 Ajax?Ajax = Asynchronous JavaScript And XML
通俗解释: Ajax 就像"外卖小哥",你在网页上点了个按钮(下单),Ajax 悄悄跑到服务器那里取数据(送餐),拿回来后更新页面(送到你手上),整个过程你不用刷新页面!
Ajax 的优势✅ 无需刷新页面 - 用户体验更好✅ 节省带宽 - 只传输需要的数据✅ 提高性能 - 减少服务器负担✅ 实时交互 - 即时获取最新数据Ajax 能做什么?
// 常见应用场景
- 搜索建议(输入时实时显示)
- 无限滚动(微博、朋友圈)
- 表单验证(检查用户名是否存在)
- 购物车更新(不刷新页面添加商品)
- 聊天应用(实时收发消息)
- 天气查询(获取实时天气)
XMLHttpRequest 详解基础用法 - 一步步学会
// 第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(请求状态)
// 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 请求完整示例
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 请求 - 发送数据
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: 'xiaoming@example.com'
,
age: 25
}
;
postData('https://jsonplaceholder.typicode.com/users'
, newUser, (error, response
) =>
{
if (error) {
console.error('创建用户失败:'
, error.message)
;
}
else {
console.log('创建成功:'
, response)
;
}
}
)
;
封装成通用函数
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 请求
// 基础用法
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 请求
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 方法
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 数据(最常用)
// 发送 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;
}
表单数据
// 发送表单数据
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(
)
;
}
文件上传
// 单文件上传
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)
;
}
}
}
)
;
处理其他格式
// 获取纯文本
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 状态码详解
// 常见状态码及其含义
const statusCodes = {
200: '成功'
,
201: '创建成功'
,
204: '删除成功(无内容)'
,
400: '请求参数错误'
,
401: '未授权'
,
403: '禁止访问'
,
404: '资源不存在'
,
500: '服务器内部错误'
,
502: '网关错误'
,
503: '服务不可用'
}
;
function getStatusMessage(status
) {
return statusCodes[status] || '未知错误'
;
}
完善的错误处理
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)
;
}
}
}
重试机制
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 高级技巧取消请求
// 使用 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)
;
}
}
)
;
请求缓存
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
)
;
并发控制
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(
)
;
实战项目案例用户管理系统
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 =>
`
${user.name
}
邮箱:${user.email
}
年龄:${user.age
}
`
).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(
)
;
搜索建议功能
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
) =>
`
${
this.highlightQuery(item.text)
}
${item.count
} 个结果
`
).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, '$1'
)
;
}
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. 安全性考虑
// 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. 性能优化
// 请求去重
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. 错误监控
// 全局错误监控
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. 通用工具函数
// 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,代码更易读✅ 实现加载状态,让用户知道正在处理✅ 适当使用缓存,提升性能✅ 考虑网络状况,处理离线场景调试技巧
// 在浏览器控制台查看网络请求
// 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 开发的核心技术,多练习、多实战才能真正掌握! ?