当前位置: 首页 > article >正文

ajax学习手册

Ajax 通俗易懂学习手册

目录

  1. Ajax 基础概念
  2. XMLHttpRequest 详解
  3. Fetch API (现代方式)
  4. 处理不同数据格式
  5. 错误处理和状态码
  6. Ajax 高级技巧
  7. 实战项目案例
  8. 最佳实践

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-Typeif (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}`);}// 如果没有内容,返回 nullif (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 => `<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();

搜索建议功能

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. 安全性考虑

// 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 学习路径

  1. 掌握基础 - XMLHttpRequest 和 Fetch API
  2. 理解概念 - 同步、异步、状态码、错误处理
  3. 实践应用 - 表单提交、文件上传、数据获取
  4. 高级技巧 - 缓存、重试、并发控制、请求取消
  5. 项目实战 - 结合实际业务场景

开发建议

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

相关文章:

ajax学习手册

Ajax 通俗易懂学习手册 目录 Ajax 基础概念XMLHttpRequest 详解Fetch API (现代方式)处理不同数据格式错误处理和状态码Ajax 高级技巧实战项目案例最佳实践 Ajax 基础概念 什么是 Ajax&#xff1f; Ajax Asynchronous JavaScript And XML 通俗解释&#xff1a; Ajax 就像…...

Python爬虫实战:研究urlunparse函数相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上的数据量呈现出指数级增长。如何从海量的网页数据中高效地获取有价值的信息,成为了学术界和工业界共同关注的问题。网络爬虫作为一种自动获取网页内容的技术,能够按照预定的规则遍历互联网上的网页,并提取出所需…...

[蓝桥杯]采油

采油 题目描述 LQ 公司是世界著名的石油公司&#xff0c;为世界供应优质石油。 最近&#xff0c;LQ 公司又在森林里发现了一大片区域的油田&#xff0c;可以在这个油田中开采 nn 个油井。 LQ 公司在这 nn 个油井之间修建了 n−1n−1 条道路&#xff0c;每条道路连接两个油井…...

OpenLayers 地图定位

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图定位功能很常见&#xff0c;在移动端和PC端都需要经常用到&#xff0c;像百度、高德、谷歌都提供了方便快捷的定位功能。OpenLayers中也提供了定位的…...

黑龙江云前沿服务器租用:便捷高效的灵活之选​

服务器租用&#xff0c;即企业直接从互联网数据中心&#xff08;IDC&#xff09;提供商处租赁服务器。企业只需按照所选的服务器配置和租赁期限&#xff0c;定期支付租金&#xff0c;即可使用服务器开展业务。​ 便捷快速部署&#xff1a;租用服务器能极大地缩短服务器搭建周期…...

PyTorch中matmul函数使用详解和示例代码

torch.matmul 是 PyTorch 中用于执行矩阵乘法的函数&#xff0c;它根据输入张量的维度自动选择适当的矩阵乘法方式&#xff0c;包括&#xff1a; 向量内积&#xff08;1D 1D&#xff09;矩阵乘向量&#xff08;2D 1D&#xff09;向量乘矩阵&#xff08;1D 2D&#xff09;矩…...

论文解读:Locating and Editing Factual Associations in GPT(ROME)

论文发表于人工智能顶会NeurIPS(原文链接)&#xff0c;研究了GPT(Generative Pre-trained Transformer)中事实关联的存储和回忆&#xff0c;发现这些关联与局部化、可直接编辑的计算相对应。因此&#xff1a; 1、开发了一种因果干预方法&#xff0c;用于识别对模型的事实预测起…...

NoSQl之Redis部署

一、Redis 核心概念与技术定位 1. 数据库分类与 Redis 的诞生背景 关系型数据库的局限性 数据模型&#xff1a;基于二维表结构&#xff0c;通过 SQL 操作&#xff0c;强一致性&#xff08;ACID 特性&#xff09;&#xff0c;适合结构化事务场景&#xff08;如银行转账、订单管…...

学习设计模式《十二》——命令模式

一、基础概念 命令模式的本质是【封装请求】命令模式的关键是把请求封装成为命令对象&#xff0c;然后就可以对这个命令对象进行一系列的处理&#xff08;如&#xff1a;参数化配置、可撤销操作、宏命令、队列请求、日志请求等&#xff09;。 命令模式的定义&#xff1a;将一个…...

十三、【核心功能篇】测试计划管理:组织和编排测试用例

【核心功能篇】测试计划管理&#xff1a;组织和编排测试用例 前言准备工作第一部分&#xff1a;后端实现 (Django)1. 定义 TestPlan 模型2. 生成并应用数据库迁移3. 创建 TestPlanSerializer4. 创建 TestPlanViewSet5. 注册路由6. 注册到 Django Admin 第二部分&#xff1a;前端…...

手撕 K-Means

1. K-means 的原理 K-means 是一种经典的无监督学习算法&#xff0c;用于将数据集划分为 kk 个簇&#xff08;cluster&#xff09;。其核心思想是通过迭代优化&#xff0c;将数据点分配到最近的簇中心&#xff0c;并更新簇中心&#xff0c;直到簇中心不再变化或达到最大迭代次…...

SmolVLA: 让机器人更懂 “看听说做” 的轻量化解决方案

&#x1f9ed; TL;DR 今天&#xff0c;我们希望向大家介绍一个新的模型: SmolVLA&#xff0c;这是一个轻量级 (450M 参数) 的开源视觉 - 语言 - 动作 (VLA) 模型&#xff0c;专为机器人领域设计&#xff0c;并且可以在消费级硬件上运行。 SmolVLAhttps://hf.co/lerobot/smolvla…...

day45python打卡

知识点回顾&#xff1a; tensorboard的发展历史和原理tensorboard的常见操作tensorboard在cifar上的实战&#xff1a;MLP和CNN模型 效果展示如下&#xff0c;很适合拿去组会汇报撑页数&#xff1a; 作业&#xff1a;对resnet18在cifar10上采用微调策略下&#xff0c;用tensorbo…...

AIGC赋能前端开发

一、引言&#xff1a;AIGC对前端开发的影响 1. AIGC与前端开发的关系 从“写代码”到“生成代码”传统开发痛点&#xff1a;重复性编码工作、UI 设计稿还原、问题定位与调试...核心场景的AI化&#xff1a;需求转代码&#xff08;P2C&#xff09;、设计稿转代码&#xff08;D2…...

Web 3D协作平台开发案例:构建制造业远程设计与可视化协作

HOOPS Communicator为开发者提供了丰富的定制化能力&#xff0c;助力他们在实现强大 Web 3D 可视化功能的同时&#xff0c;灵活构建符合特定业务需求的工程应用。对于希望构建在线协同设计工具的企业而言&#xff0c;如何在保障性能与用户体验的前提下实现高效开发&#xff0c;…...

AI Agent开发第78课-大模型结合Flink构建政务类长公文、长文件、OA应用Agent

开篇 AI Agent2025确定是进入了爆发期,到处都在冒出各种各样的实用AI Agent。很多人、组织都投身于开发AI Agent。 但是从3月份开始业界开始出现了一种这样的声音: AI开发入门并不难,一旦开发完后没法用! 经历过至少一个AI Agent从开发到上线的小伙伴们其实都听到过这种…...

极空间z4pro配置gitea mysql,内网穿透

极空间z4pro配置gitea mysql等记录&#xff0c;内网穿透 1、mysql、gitea镜像下载&#xff0c;极空间不成功&#xff0c;先用自己电脑科学后下载镜像,拉取代码&#xff1a; docker pull --platform linux/amd64 gitea/gitea:1.23 docker pull --platform linux/amd64 mysql:5.…...

第三方测试机构进行科技成果鉴定测试有什么价值

在当今科技创新的浪潮中&#xff0c;科技成果的鉴定测试至关重要&#xff0c;而第三方测试机构凭借其独特优势&#xff0c;在这一领域发挥着不可替代的作用。那么&#xff0c;第三方测试机构进行科技成果鉴定测试究竟有什么价值呢&#xff1f; 一、第三方测试机构能提供独立、公…...

华为云Flexus+DeepSeek征文|基于华为云Flexus X和DeepSeek-R1打造个人知识库问答系统

目录 前言 1 快速部署&#xff1a;一键搭建Dify平台 1.1 部署流程详解 1.2 初始配置与登录 2 构建专属知识库 2.1 进入知识库模块并创建新库 2.2 选择数据源导入内容 2.3 上传并识别多种文档格式 2.4 文本处理与索引构建 2.5 保存并完成知识库创建 3接入ModelArts S…...

【数据结构】_排序

【本节目标】 排序的概念及其运用常见排序算法的实现排序算法复杂度及稳定性分析 1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 1.2特性…...

《前端面试题:JS数据类型》

JavaScript 数据类型指南&#xff1a;从基础到高级全解析 一、JavaScript 数据类型概述 JavaScript 作为一门动态类型语言&#xff0c;其数据类型系统是理解这门语言的核心基础。在 ECMAScript 标准中&#xff0c;数据类型分为两大类&#xff1a; 1. 原始类型&#xff08;Pr…...

PPT转图片拼贴工具 v4.3

软件介绍 这个软件就是将PPT文件转换为图片并且拼接起来。 效果展示 支持导入文件和支持导入文件夹&#xff0c;也支持手动输入文件/文件夹路径 软件界面 这一次提供了源码和开箱即用版本&#xff0c;exe就是直接用就可以了。 软件源码 import os import re import sys …...

Chrome安装代理插件ZeroOmega(保姆级别)

目录 本文直接讲解一下怎么本地安装ZeroOmega一、下载文件在GitHub直接下ZeroOmega 的文件&#xff08;下最新版即可&#xff09; 二、安装插件打开 Chrome 浏览器&#xff0c;访问 chrome://extensions/ 页面&#xff08;扩展程序管理页面&#xff09;&#xff0c;并打开开发者…...

Transformer-BiGRU多变量时序预测(Matlab完整源码和数据)

Transformer-BiGRU多变量时序预测&#xff08;Matlab完整源码和数据&#xff09; 目录 Transformer-BiGRU多变量时序预测&#xff08;Matlab完整源码和数据&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现Transformer-BiGRU多变量时间序列预测&…...

新华三H3CNE网络工程师认证—Easy IP

Easy IP 就是“用路由器自己的公网IP&#xff0c;给全家所有设备当共享门牌号”的技术&#xff01;&#xff08;省掉额外公网IP&#xff0c;省钱又省配置&#xff01;&#xff09; 生活场景对比&#xff0c;想象你住在一个小区&#xff1a;普通动态NAT&#xff1a;物业申请了 …...

《视觉SLAM十四讲》自用笔记 第二讲:SLAM系统概述

在rm队伍里作为算法组梯队队员度过了一个赛季&#xff0c;为了促进和负责其他工作的算法组成员的交流&#xff0c;我决定在接下来的半个学期里&#xff08;可能更快&#xff09;读完这本书&#xff0c;并将其中的部分理论应用于我自制的雷达导航小车上。 以下为第二讲的部分笔记…...

vscode 插件 eslint, 检查 js 语法

1. 起因&#xff0c; 目的: 我的需求 vscode 写js代码&#xff0c; 有什么插件能进行语法检查。 比如某个函数没有定义&#xff0c;getName(), 但是却调用了。 那么这个插件会给出警告&#xff0c;在 getName() 给出红色波浪线。类似这种效果的插件&#xff0c; 有吗&#xf…...

Excel 模拟分析之单变量求解简单应用

正向求解 利用公式根据贷款总额、还款期限、贷款利率&#xff0c;求每月还款金额 反向求解 根据每月还款能力&#xff0c;求最大能承受贷款金额 参数&#xff1a; 目标单元格&#xff1a;求的值所在的单元格 目标值&#xff1a;想要达到的预期值 可变单元格&#xff1a;变…...

装备制造项目管理具备什么特征?如何选择适配的项目管理软件系统进行项目管控?

国内某大型半导体装备制造企业与奥博思软件达成战略合作&#xff0c;全面引入奥博思 PowerProject 打造企业专属项目管理平台&#xff0c;进一步提升智能制造领域的项目管理效率与协同能力。 该项目管理平台聚焦半导体装备研发与制造的业务特性&#xff0c;实现了从项目立项、…...

FPGA 动态重构配置流程

触发FPGA 进行配置的方式有两种&#xff0c;一种是断电后上电&#xff0c;另一种是在FPGA运行过程中&#xff0c;将PROGRAM 管脚拉低。将PROGRAM 管脚拉低500ns 以上就可以触发FPGA 进行重构。 FPGA 的配置过程大致可以分为&#xff1a;配置的触发和建立阶段、加载配置文件和建…...