ajax学习手册

Ajax 通俗易懂学习手册

目录

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

Ajax 基础概念

什么是 Ajax?

Ajax = A synchronous J avaScript A nd XML

通俗解释: Ajax 就像"外卖小哥",你在网页上点了个按钮(下单),Ajax 悄悄跑到服务器那里取数据(送餐),拿回来后更新页面(送到你手上),整个过程你不用刷新页面!

Ajax 的优势

  • 无需刷新页面 - 用户体验更好
  • 节省带宽 - 只传输需要的数据
  • 提高性能 - 减少服务器负担
  • 实时交互 - 即时获取最新数据

Ajax 能做什么?

javascript 复制代码
// 常见应用场景
- 搜索建议(输入时实时显示)
- 无限滚动(微博、朋友圈)
- 表单验证(检查用户名是否存在)
- 购物车更新(不刷新页面添加商品)
- 聊天应用(实时收发消息)
- 天气查询(获取实时天气)

XMLHttpRequest 详解

基础用法 - 一步步学会

javascript 复制代码
// 第1步:创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();

// 第2步:配置请求
xhr.open('GET', 'https://api.example.com/users', true);
// 参数说明:
// - 'GET': 请求方法
// - URL: 请求地址
// - true: 是否异步(几乎总是true)

// 第3步:设置响应处理
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log('请求成功!');
        const data = JSON.parse(xhr.responseText);
        console.log(data);
    }
};

// 第4步:发送请求
xhr.send();

理解 readyState(请求状态)

javascript 复制代码
// readyState 的5种状态
0: UNSENT - 请求未初始化
1: OPENED - 连接已建立
2: HEADERS_RECEIVED - 请求已接收
3: LOADING - 请求处理中
4: DONE - 请求已完成

// 实际使用中的状态检查
xhr.onreadystatechange = function() {
    console.log('当前状态:', xhr.readyState);
    
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.log('成功!', xhr.responseText);
        } else {
            console.log('出错了,状态码:', xhr.status);
        }
    }
};

GET 请求完整示例

javascript 复制代码
function getData(url, callback) {
    const xhr = new XMLHttpRequest();
    
    xhr.open('GET', url, true);
    
    // 设置请求头(可选)
    xhr.setRequestHeader('Content-Type', 'application/json');
    
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                const data = JSON.parse(xhr.responseText);
                callback(null, data); // 成功
            } else {
                callback(new Error(`请求失败: ${xhr.status}`)); // 失败
            }
        }
    };
    
    xhr.send();
}

// 使用示例
getData('https://jsonplaceholder.typicode.com/posts', (error, data) => {
    if (error) {
        console.error('出错了:', error.message);
    } else {
        console.log('获取到的数据:', data);
    }
});

POST 请求 - 发送数据

javascript 复制代码
function postData(url, data, callback) {
    const xhr = new XMLHttpRequest();
    
    xhr.open('POST', url, true);
    
    // POST 请求必须设置这个头部
    xhr.setRequestHeader('Content-Type', 'application/json');
    
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200 || xhr.status === 201) {
                const response = JSON.parse(xhr.responseText);
                callback(null, response);
            } else {
                callback(new Error(`请求失败: ${xhr.status}`));
            }
        }
    };
    
    // 发送 JSON 数据
    xhr.send(JSON.stringify(data));
}

// 使用示例:创建新用户
const newUser = {
    name: '小明',
    email: '[email protected]',
    age: 25
};

postData('https://jsonplaceholder.typicode.com/users', newUser, (error, response) => {
    if (error) {
        console.error('创建用户失败:', error.message);
    } else {
        console.log('创建成功:', response);
    }
});

封装成通用函数

javascript 复制代码
function ajax(options) {
    // 默认设置
    const defaults = {
        method: 'GET',
        url: '',
        data: null,
        headers: {},
        timeout: 5000,
        success: function() {},
        error: function() {}
    };
    
    // 合并配置
    const config = { ...defaults, ...options };
    
    const xhr = new XMLHttpRequest();
    
    // 设置超时
    xhr.timeout = config.timeout;
    
    xhr.open(config.method, config.url, true);
    
    // 设置请求头
    for (let key in config.headers) {
        xhr.setRequestHeader(key, config.headers[key]);
    }
    
    // 如果是 POST 请求且有数据,设置默认 Content-Type
    if (config.method === 'POST' && config.data && !config.headers['Content-Type']) {
        xhr.setRequestHeader('Content-Type', 'application/json');
    }
    
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 200 && xhr.status < 300) {
                let response = xhr.responseText;
                try {
                    response = JSON.parse(response);
                } catch (e) {
                    // 如果不是 JSON,保持原样
                }
                config.success(response);
            } else {
                config.error(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
            }
        }
    };
    
    xhr.ontimeout = function() {
        config.error(new Error('请求超时'));
    };
    
    // 发送数据
    let sendData = config.data;
    if (sendData && typeof sendData === 'object') {
        sendData = JSON.stringify(sendData);
    }
    
    xhr.send(sendData);
}

// 使用示例
ajax({
    method: 'GET',
    url: 'https://jsonplaceholder.typicode.com/posts/1',
    success: function(data) {
        console.log('获取成功:', data);
    },
    error: function(error) {
        console.error('请求失败:', error.message);
    }
});

Fetch API (现代方式)

为什么用 Fetch?

Fetch 是现代浏览器提供的新 API,比 XMLHttpRequest 更简洁、更强大!

优势:

  • ✅ 基于 Promise,支持 async/await
  • ✅ 语法更简洁
  • ✅ 更好的错误处理
  • ✅ 支持流式数据

基础 GET 请求

javascript 复制代码
// 基础用法
fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('出错了:', error));

// 使用 async/await(推荐)
async function fetchData() {
    try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('出错了:', error);
    }
}

fetchData();

POST 请求

javascript 复制代码
async function createPost(postData) {
    try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(postData)
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const result = await response.json();
        console.log('创建成功:', result);
        return result;
    } catch (error) {
        console.error('创建失败:', error);
    }
}

// 使用示例
createPost({
    title: '我的第一篇博客',
    body: '这是内容...',
    userId: 1
});

常用的 HTTP 方法

javascript 复制代码
class ApiService {
    constructor(baseURL) {
        this.baseURL = baseURL;
    }
    
    // GET - 获取数据
    async get(endpoint) {
        const response = await fetch(`${this.baseURL}${endpoint}`);
        return this.handleResponse(response);
    }
    
    // POST - 创建数据
    async post(endpoint, data) {
        const response = await fetch(`${this.baseURL}${endpoint}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        return this.handleResponse(response);
    }
    
    // PUT - 更新数据
    async put(endpoint, data) {
        const response = await fetch(`${this.baseURL}${endpoint}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        return this.handleResponse(response);
    }
    
    // DELETE - 删除数据
    async delete(endpoint) {
        const response = await fetch(`${this.baseURL}${endpoint}`, {
            method: 'DELETE'
        });
        return this.handleResponse(response);
    }
    
    // 统一处理响应
    async handleResponse(response) {
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        // 如果没有内容,返回 null
        if (response.status === 204) {
            return null;
        }
        
        return await response.json();
    }
}

// 使用示例
const api = new ApiService('https://jsonplaceholder.typicode.com');

async function example() {
    try {
        // 获取所有文章
        const posts = await api.get('/posts');
        console.log('所有文章:', posts);
        
        // 创建新文章
        const newPost = await api.post('/posts', {
            title: '新文章',
            body: '文章内容',
            userId: 1
        });
        console.log('新文章:', newPost);
        
        // 更新文章
        const updatedPost = await api.put('/posts/1', {
            id: 1,
            title: '更新的标题',
            body: '更新的内容',
            userId: 1
        });
        console.log('更新后:', updatedPost);
        
        // 删除文章
        await api.delete('/posts/1');
        console.log('删除成功');
        
    } catch (error) {
        console.error('操作失败:', error.message);
    }
}

处理不同数据格式

JSON 数据(最常用)

javascript 复制代码
// 发送 JSON
async function sendJSON(data) {
    const response = await fetch('/api/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
    });
    
    return await response.json();
}

// 接收 JSON
async function receiveJSON() {
    const response = await fetch('/api/users');
    const data = await response.json();
    return data;
}

表单数据

javascript 复制代码
// 发送表单数据
async function sendFormData(formElement) {
    const formData = new FormData(formElement);
    
    const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData // 注意:不要设置 Content-Type,让浏览器自动设置
    });
    
    return await response.json();
}

// 手动创建表单数据
async function sendCustomFormData() {
    const formData = new FormData();
    formData.append('name', '小明');
    formData.append('age', '25');
    formData.append('avatar', fileInput.files[0]); // 文件
    
    const response = await fetch('/api/profile', {
        method: 'POST',
        body: formData
    });
    
    return await response.json();
}

文件上传

javascript 复制代码
// 单文件上传
async function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    
    try {
        const response = await fetch('/api/upload', {
            method: 'POST',
            body: formData
        });
        
        if (!response.ok) {
            throw new Error('上传失败');
        }
        
        const result = await response.json();
        console.log('上传成功:', result);
        return result;
    } catch (error) {
        console.error('上传出错:', error);
    }
}

// 多文件上传
async function uploadMultipleFiles(files) {
    const formData = new FormData();
    
    for (let i = 0; i < files.length; i++) {
        formData.append('files', files[i]);
    }
    
    const response = await fetch('/api/upload-multiple', {
        method: 'POST',
        body: formData
    });
    
    return await response.json();
}

// 带进度的文件上传(使用 XMLHttpRequest)
function uploadWithProgress(file, onProgress) {
    return new Promise((resolve, reject) => {
        const formData = new FormData();
        formData.append('file', file);
        
        const xhr = new XMLHttpRequest();
        
        // 上传进度
        xhr.upload.addEventListener('progress', (e) => {
            if (e.lengthComputable) {
                const percentComplete = (e.loaded / e.total) * 100;
                onProgress(percentComplete);
            }
        });
        
        xhr.addEventListener('load', () => {
            if (xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(new Error('上传失败'));
            }
        });
        
        xhr.addEventListener('error', () => {
            reject(new Error('网络错误'));
        });
        
        xhr.open('POST', '/api/upload');
        xhr.send(formData);
    });
}

// 使用示例
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
    const file = e.target.files[0];
    if (file) {
        try {
            const result = await uploadWithProgress(file, (progress) => {
                console.log(`上传进度:${progress.toFixed(1)}%`);
            });
            console.log('上传完成:', result);
        } catch (error) {
            console.error('上传失败:', error);
        }
    }
});

处理其他格式

javascript 复制代码
// 获取纯文本
async function getText() {
    const response = await fetch('/api/readme.txt');
    const text = await response.text();
    return text;
}

// 获取二进制数据
async function getBinaryData() {
    const response = await fetch('/api/image.jpg');
    const blob = await response.blob();
    return blob;
}

// 获取数组缓冲区
async function getArrayBuffer() {
    const response = await fetch('/api/data.bin');
    const buffer = await response.arrayBuffer();
    return buffer;
}

错误处理和状态码

HTTP 状态码详解

javascript 复制代码
// 常见状态码及其含义
const statusCodes = {
    200: '成功',
    201: '创建成功',
    204: '删除成功(无内容)',
    400: '请求参数错误',
    401: '未授权',
    403: '禁止访问',
    404: '资源不存在',
    500: '服务器内部错误',
    502: '网关错误',
    503: '服务不可用'
};

function getStatusMessage(status) {
    return statusCodes[status] || '未知错误';
}

完善的错误处理

javascript 复制代码
class ApiError extends Error {
    constructor(status, message, data = null) {
        super(message);
        this.name = 'ApiError';
        this.status = status;
        this.data = data;
    }
}

async function safeFetch(url, options = {}) {
    try {
        const response = await fetch(url, options);
        
        // 检查响应状态
        if (!response.ok) {
            let errorData = null;
            try {
                errorData = await response.json();
            } catch (e) {
                // 如果响应不是 JSON,忽略
            }
            
            throw new ApiError(
                response.status,
                errorData?.message || `HTTP ${response.status}: ${response.statusText}`,
                errorData
            );
        }
        
        // 根据 Content-Type 自动解析
        const contentType = response.headers.get('content-type');
        
        if (contentType && contentType.includes('application/json')) {
            return await response.json();
        } else if (contentType && contentType.includes('text/')) {
            return await response.text();
        } else {
            return await response.blob();
        }
        
    } catch (error) {
        if (error instanceof ApiError) {
            throw error;
        }
        
        // 网络错误
        if (error.name === 'TypeError') {
            throw new ApiError(0, '网络连接失败,请检查网络设置');
        }
        
        // 其他错误
        throw new ApiError(0, error.message);
    }
}

// 使用示例
async function handleApiCall() {
    try {
        const data = await safeFetch('/api/users/123');
        console.log('获取成功:', data);
    } catch (error) {
        if (error instanceof ApiError) {
            switch (error.status) {
                case 401:
                    console.error('请先登录');
                    // 跳转到登录页
                    break;
                case 403:
                    console.error('权限不足');
                    break;
                case 404:
                    console.error('用户不存在');
                    break;
                case 0:
                    console.error('网络错误:', error.message);
                    break;
                default:
                    console.error('请求失败:', error.message);
            }
        } else {
            console.error('未知错误:', error);
        }
    }
}

重试机制

javascript 复制代码
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
    let lastError;
    
    for (let i = 0; i <= maxRetries; i++) {
        try {
            const response = await fetch(url, options);
            
            // 如果是服务器错误且还有重试次数,继续重试
            if (response.status >= 500 && i < maxRetries) {
                throw new Error(`服务器错误 ${response.status}`);
            }
            
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            
            return await response.json();
            
        } catch (error) {
            lastError = error;
            
            if (i < maxRetries) {
                console.log(`第 ${i + 1} 次请求失败,${1}秒后重试...`);
                await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); // 指数退避
            }
        }
    }
    
    throw lastError;
}

// 使用示例
async function robustApiCall() {
    try {
        const data = await fetchWithRetry('/api/unstable-endpoint');
        console.log('最终成功:', data);
    } catch (error) {
        console.error('重试后仍然失败:', error.message);
    }
}

Ajax 高级技巧

取消请求

javascript 复制代码
// 使用 AbortController 取消请求
function cancelableRequest(url) {
    const controller = new AbortController();
    
    const promise = fetch(url, {
        signal: controller.signal
    }).then(response => response.json());
    
    // 返回 promise 和取消函数
    return {
        promise,
        cancel: () => controller.abort()
    };
}

// 使用示例
const { promise, cancel } = cancelableRequest('/api/slow-endpoint');

// 5秒后自动取消
setTimeout(() => {
    cancel();
    console.log('请求已取消');
}, 5000);

promise
    .then(data => console.log('获取成功:', data))
    .catch(error => {
        if (error.name === 'AbortError') {
            console.log('请求被取消了');
        } else {
            console.error('请求失败:', error);
        }
    });

请求缓存

javascript 复制代码
class ApiCache {
    constructor(expireTime = 5 * 60 * 1000) { // 默认5分钟过期
        this.cache = new Map();
        this.expireTime = expireTime;
    }
    
    // 生成缓存键
    getCacheKey(url, options = {}) {
        return JSON.stringify({ url, ...options });
    }
    
    // 获取缓存
    get(key) {
        const item = this.cache.get(key);
        if (!item) return null;
        
        // 检查是否过期
        if (Date.now() > item.expireAt) {
            this.cache.delete(key);
            return null;
        }
        
        return item.data;
    }
    
    // 设置缓存
    set(key, data) {
        this.cache.set(key, {
            data,
            expireAt: Date.now() + this.expireTime
        });
    }
    
    // 带缓存的请求
    async fetch(url, options = {}) {
        const cacheKey = this.getCacheKey(url, options);
        
        // 先查缓存
        const cached = this.get(cacheKey);
        if (cached) {
            console.log('使用缓存数据');
            return cached;
        }
        
        // 发起请求
        const response = await fetch(url, options);
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        
        const data = await response.json();
        
        // 缓存结果
        this.set(cacheKey, data);
        
        return data;
    }
    
    // 清除缓存
    clear() {
        this.cache.clear();
    }
}

// 使用示例
const apiCache = new ApiCache();

async function getCachedData() {
    try {
        const data = await apiCache.fetch('/api/users');
        console.log('数据:', data);
    } catch (error) {
        console.error('获取失败:', error);
    }
}

// 第一次调用会发起请求
getCachedData();

// 第二次调用会使用缓存
setTimeout(() => {
    getCachedData(); // 使用缓存
}, 1000);

并发控制

javascript 复制代码
class RequestQueue {
    constructor(maxConcurrent = 3) {
        this.maxConcurrent = maxConcurrent;
        this.running = 0;
        this.queue = [];
    }
    
    async add(requestFn) {
        return new Promise((resolve, reject) => {
            this.queue.push({
                requestFn,
                resolve,
                reject
            });
            this.process();
        });
    }
    
    async process() {
        if (this.running >= this.maxConcurrent || this.queue.length === 0) {
            return;
        }
        
        this.running++;
        const { requestFn, resolve, reject } = this.queue.shift();
        
        try {
            const result = await requestFn();
            resolve(result);
        } catch (error) {
            reject(error);
        } finally {
            this.running--;
            this.process(); // 处理下一个请求
        }
    }
}

// 使用示例
const requestQueue = new RequestQueue(2); // 最多同时2个请求

async function batchRequests() {
    const urls = [
        '/api/user/1',
        '/api/user/2',
        '/api/user/3',
        '/api/user/4',
        '/api/user/5'
    ];
    
    const promises = urls.map(url => 
        requestQueue.add(() => fetch(url).then(r => r.json()))
    );
    
    try {
        const results = await Promise.all(promises);
        console.log('所有请求完成:', results);
    } catch (error) {
        console.error('批量请求失败:', error);
    }
}

batchRequests();

实战项目案例

用户管理系统

javascript 复制代码
class UserManager {
    constructor() {
        this.baseURL = '/api/users';
        this.users = [];
        this.init();
    }
    
    async init() {
        await this.loadUsers();
        this.bindEvents();
    }
    
    // 加载用户列表
    async loadUsers() {
        try {
            const response = await fetch(this.baseURL);
            this.users = await response.json();
            this.renderUsers();
        } catch (error) {
            this.showError('加载用户失败:' + error.message);
        }
    }
    
    // 创建用户
    async createUser(userData) {
        try {
            const response = await fetch(this.baseURL, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(userData)
            });
            
            if (!response.ok) {
                throw new Error('创建失败');
            }
            
            const newUser = await response.json();
            this.users.push(newUser);
            this.renderUsers();
            this.showSuccess('用户创建成功!');
            
        } catch (error) {
            this.showError('创建用户失败:' + error.message);
        }
    }
    
    // 更新用户
    async updateUser(id, userData) {
        try {
            const response = await fetch(`${this.baseURL}/${id}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(userData)
            });
            
            if (!response.ok) {
                throw new Error('更新失败');
            }
            
            const updatedUser = await response.json();
            const index = this.users.findIndex(u => u.id === id);
            if (index !== -1) {
                this.users[index] = updatedUser;
                this.renderUsers();
                this.showSuccess('用户更新成功!');
            }
            
        } catch (error) {
            this.showError('更新用户失败:' + error.message);
        }
    }
    
    // 删除用户
    async deleteUser(id) {
        if (!confirm('确定要删除这个用户吗?')) {
            return;
        }
        
        try {
            const response = await fetch(`${this.baseURL}/${id}`, {
                method: 'DELETE'
            });
            
            if (!response.ok) {
                throw new Error('删除失败');
            }
            
            this.users = this.users.filter(u => u.id !== id);
            this.renderUsers();
            this.showSuccess('用户删除成功!');
            
        } catch (error) {
            this.showError('删除用户失败:' + error.message);
        }
    }
    
    // 渲染用户列表
    renderUsers() {
        const container = document.getElementById('userList');
        container.innerHTML = this.users.map(user => `
            <div class="user-item" data-id="${user.id}">
                <h3>${user.name}</h3>
                <p>邮箱:${user.email}</p>
                <p>年龄:${user.age}</p>
                <button onclick="userManager.editUser(${user.id})">编辑</button>
                <button onclick="userManager.deleteUser(${user.id})">删除</button>
            </div>
        `).join('');
    }
    
    // 绑定事件
    bindEvents() {
        // 创建用户表单
        document.getElementById('createForm').addEventListener('submit', async (e) => {
            e.preventDefault();
            const formData = new FormData(e.target);
            const userData = {
                name: formData.get('name'),
                email: formData.get('email'),
                age: parseInt(formData.get('age'))
            };
            await this.createUser(userData);
            e.target.reset();
        });
    }
    
    // 编辑用户
    editUser(id) {
        const user = this.users.find(u => u.id === id);
        if (user) {
            document.getElementById('editName').value = user.name;
            document.getElementById('editEmail').value = user.email;
            document.getElementById('editAge').value = user.age;
            document.getElementById('editModal').style.display = 'block';
            document.getElementById('editForm').onsubmit = async (e) => {
                e.preventDefault();
                const formData = new FormData(e.target);
                const userData = {
                    name: formData.get('name'),
                    email: formData.get('email'),
                    age: parseInt(formData.get('age'))
                };
                await this.updateUser(id, userData);
                document.getElementById('editModal').style.display = 'none';
            };
        }
    }
    
    // 显示成功消息
    showSuccess(message) {
        this.showMessage(message, 'success');
    }
    
    // 显示错误消息
    showError(message) {
        this.showMessage(message, 'error');
    }
    
    // 显示消息
    showMessage(message, type) {
        const messageDiv = document.createElement('div');
        messageDiv.className = `message ${type}`;
        messageDiv.textContent = message;
        document.body.appendChild(messageDiv);
        
        setTimeout(() => {
            messageDiv.remove();
        }, 3000);
    }
}

// 初始化
const userManager = new UserManager();

搜索建议功能

javascript 复制代码
class SearchSuggestion {
    constructor(inputId, suggestionsId) {
        this.input = document.getElementById(inputId);
        this.suggestionsContainer = document.getElementById(suggestionsId);
        this.cache = new Map();
        this.currentRequest = null;
        this.debounceTimer = null;
        
        this.init();
    }
    
    init() {
        this.input.addEventListener('input', (e) => {
            this.debounceSearch(e.target.value);
        });
        
        this.input.addEventListener('keydown', (e) => {
            this.handleKeyboard(e);
        });
        
        // 点击外部关闭建议
        document.addEventListener('click', (e) => {
            if (!this.input.contains(e.target) && !this.suggestionsContainer.contains(e.target)) {
                this.hideSuggestions();
            }
        });
    }
    
    // 防抖搜索
    debounceSearch(query) {
        clearTimeout(this.debounceTimer);
        this.debounceTimer = setTimeout(() => {
            this.search(query);
        }, 300);
    }
    
    async search(query) {
        if (!query.trim()) {
            this.hideSuggestions();
            return;
        }
        
        // 取消之前的请求
        if (this.currentRequest) {
            this.currentRequest.abort();
        }
        
        // 检查缓存
        if (this.cache.has(query)) {
            this.showSuggestions(this.cache.get(query));
            return;
        }
        
        try {
            const controller = new AbortController();
            this.currentRequest = controller;
            
            const response = await fetch(`/api/search/suggestions?q=${encodeURIComponent(query)}`, {
                signal: controller.signal
            });
            
            if (!response.ok) {
                throw new Error('搜索失败');
            }
            
            const suggestions = await response.json();
            
            // 缓存结果
            this.cache.set(query, suggestions);
            
            this.showSuggestions(suggestions);
            
        } catch (error) {
            if (error.name !== 'AbortError') {
                console.error('搜索出错:', error);
            }
        } finally {
            this.currentRequest = null;
        }
    }
    
    showSuggestions(suggestions) {
        if (suggestions.length === 0) {
            this.hideSuggestions();
            return;
        }
        
        const html = suggestions.map((item, index) => `
            <div class="suggestion-item" data-index="${index}" onclick="searchSuggestion.selectSuggestion('${item.text}')">
                <span class="suggestion-text">${this.highlightQuery(item.text)}</span>
                <span class="suggestion-count">${item.count} 个结果</span>
            </div>
        `).join('');
        
        this.suggestionsContainer.innerHTML = html;
        this.suggestionsContainer.style.display = 'block';
    }
    
    hideSuggestions() {
        this.suggestionsContainer.style.display = 'none';
    }
    
    selectSuggestion(text) {
        this.input.value = text;
        this.hideSuggestions();
        this.performSearch(text);
    }
    
    highlightQuery(text) {
        const query = this.input.value.trim();
        if (!query) return text;
        
        const regex = new RegExp(`(${query})`, 'gi');
        return text.replace(regex, '<mark>$1</mark>');
    }
    
    handleKeyboard(e) {
        const items = this.suggestionsContainer.querySelectorAll('.suggestion-item');
        const current = this.suggestionsContainer.querySelector('.suggestion-item.active');
        
        switch (e.key) {
            case 'ArrowDown':
                e.preventDefault();
                if (current) {
                    current.classList.remove('active');
                    const next = current.nextElementSibling || items[0];
                    next.classList.add('active');
                } else if (items.length > 0) {
                    items[0].classList.add('active');
                }
                break;
                
            case 'ArrowUp':
                e.preventDefault();
                if (current) {
                    current.classList.remove('active');
                    const prev = current.previousElementSibling || items[items.length - 1];
                    prev.classList.add('active');
                } else if (items.length > 0) {
                    items[items.length - 1].classList.add('active');
                }
                break;
                
            case 'Enter':
                e.preventDefault();
                if (current) {
                    const text = current.querySelector('.suggestion-text').textContent;
                    this.selectSuggestion(text);
                } else {
                    this.performSearch(this.input.value);
                }
                break;
                
            case 'Escape':
                this.hideSuggestions();
                break;
        }
    }
    
    performSearch(query) {
        console.log('执行搜索:', query);
        // 这里实现实际的搜索逻辑
    }
}

// 初始化搜索建议
const searchSuggestion = new SearchSuggestion('searchInput', 'suggestions');

最佳实践

1. 安全性考虑

javascript 复制代码
// CSRF 防护
function getCSRFToken() {
    return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
}

// 带 CSRF 令牌的请求
async function secureRequest(url, options = {}) {
    const defaultHeaders = {
        'X-CSRF-TOKEN': getCSRFToken(),
        'Content-Type': 'application/json'
    };
    
    return fetch(url, {
        ...options,
        headers: {
            ...defaultHeaders,
            ...options.headers
        }
    });
}

// 验证响应数据
function validateResponse(data, schema) {
    // 简单的数据验证示例
    for (let key in schema) {
        if (schema[key].required && !data.hasOwnProperty(key)) {
            throw new Error(`缺少必需字段:${key}`);
        }
        
        if (data[key] && schema[key].type && typeof data[key] !== schema[key].type) {
            throw new Error(`字段类型错误:${key}`);
        }
    }
    
    return true;
}

2. 性能优化

javascript 复制代码
// 请求去重
class RequestDeduplicator {
    constructor() {
        this.pendingRequests = new Map();
    }
    
    async request(key, requestFn) {
        if (this.pendingRequests.has(key)) {
            return this.pendingRequests.get(key);
        }
        
        const promise = requestFn().finally(() => {
            this.pendingRequests.delete(key);
        });
        
        this.pendingRequests.set(key, promise);
        return promise;
    }
}

const deduplicator = new RequestDeduplicator();

// 使用示例
async function getUser(id) {
    return deduplicator.request(`user-${id}`, () => 
        fetch(`/api/users/${id}`).then(r => r.json())
    );
}

// 多次调用只会发起一次请求
getUser(1);
getUser(1);
getUser(1);

3. 错误监控

javascript 复制代码
// 全局错误监控
class ErrorMonitor {
    constructor() {
        this.errors = [];
        this.maxErrors = 100;
    }
    
    log(error, context = {}) {
        const errorInfo = {
            message: error.message,
            stack: error.stack,
            timestamp: new Date().toISOString(),
            url: window.location.href,
            userAgent: navigator.userAgent,
            context
        };
        
        this.errors.push(errorInfo);
        
        // 保持错误日志数量
        if (this.errors.length > this.maxErrors) {
            this.errors.shift();
        }
        
        // 上报错误(可选)
        this.reportError(errorInfo);
    }
    
    async reportError(errorInfo) {
        try {
            await fetch('/api/errors', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(errorInfo)
            });
        } catch (e) {
            console.error('错误上报失败:', e);
        }
    }
    
    getErrors() {
        return this.errors;
    }
}

const errorMonitor = new ErrorMonitor();

// 包装 fetch 以监控错误
async function monitoredFetch(url, options = {}) {
    try {
        const response = await fetch(url, options);
        
        if (!response.ok) {
            const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
            errorMonitor.log(error, { url, options, status: response.status });
            throw error;
        }
        
        return response;
    } catch (error) {
        errorMonitor.log(error, { url, options });
        throw error;
    }
}

4. 通用工具函数

javascript 复制代码
// Ajax 工具库
const AjaxUtils = {
    // 构建查询字符串
    buildQuery(params) {
        return new URLSearchParams(params).toString();
    },
    
    // 解析响应头
    parseHeaders(response) {
        const headers = {};
        for (let [key, value] of response.headers.entries()) {
            headers[key] = value;
        }
        return headers;
    },
    
    // 检查网络状态
    isOnline() {
        return navigator.onLine;
    },
    
    // 等待网络恢复
    waitForOnline() {
        return new Promise(resolve => {
            if (this.isOnline()) {
                resolve();
            } else {
                const handler = () => {
                    if (this.isOnline()) {
                        window.removeEventListener('online', handler);
                        resolve();
                    }
                };
                window.addEventListener('online', handler);
            }
        });
    },
    
    // 格式化文件大小
    formatFileSize(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
};

总结

Ajax 学习路径

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

开发建议

  • 优先使用 Fetch API,语法更简洁
  • 总是处理错误,提供友好的用户体验
  • 使用 async/await,代码更易读
  • 实现加载状态,让用户知道正在处理
  • 适当使用缓存,提升性能
  • 考虑网络状况,处理离线场景

调试技巧

javascript 复制代码
// 在浏览器控制台查看网络请求
// 1. 打开开发者工具 (F12)
// 2. 切换到 Network 选项卡
// 3. 重新发起请求,查看详细信息

// 使用 console.log 调试
fetch('/api/data')
    .then(response => {
        console.log('响应状态:', response.status);
        console.log('响应头:', response.headers);
        return response.json();
    })
    .then(data => {
        console.log('响应数据:', data);
    });

记住:Ajax 是现代 Web 开发的核心技术,多练习、多实战才能真正掌握! 🚀

相关推荐
萌新小码农‍5 小时前
Spring框架学习day7--SpringWeb学习(概念与搭建配置)
学习·spring·状态模式
蓝婷儿5 小时前
6个月Python学习计划 Day 15 - 函数式编程、高阶函数、生成器/迭代器
开发语言·python·学习
行云流水剑5 小时前
【学习记录】深入解析 AI 交互中的五大核心概念:Prompt、Agent、MCP、Function Calling 与 Tools
人工智能·学习·交互
一弓虽5 小时前
zookeeper 学习
分布式·学习·zookeeper
苗老大5 小时前
MMRL: Multi-Modal Representation Learning for Vision-Language Models(多模态表示学习)
人工智能·学习·语言模型
xhyu616 小时前
【学习笔记】On the Biology of a Large Language Model
笔记·学习·语言模型
小白杨树树6 小时前
【SSM】SpringMVC学习笔记7:前后端数据传输协议和异常处理
笔记·学习
知月玄9 小时前
网页前端开发(基础进阶4--axios)
okhttp
dddaidai1239 小时前
kafka入门学习
分布式·学习·kafka