完整教程:ajax学习手册

完整教程:ajax学习手册

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 开发的核心技术,多练习、多实战才能真正掌握! ?