ajax学习手册

news2025/6/7 23:52:57

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2403488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

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

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

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

一、基础概念 命令模式的本质是【封装请求】命令模式的关键是把请求封装成为命令对象&#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从开发到上线的小伙伴们其实都听到过这种…

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

在当今科技创新的浪潮中&#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特性…

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;物业申请了 …

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

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

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

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

FPGA 动态重构配置流程

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