欢迎光临韵绾网
详情描述
Ajax 封装详解

1. 什么是 Ajax?

Ajax(Asynchronous JavaScript and XML)是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过异步方式与服务器交换数据,实现页面的局部刷新。

2. 原生 Ajax 的基本使用

// 原生 Ajax 示例
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);

xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.log(JSON.parse(xhr.responseText));
        } else {
            console.error('请求失败');
        }
    }
};

xhr.send();

3. 为什么要封装 Ajax?

原生 Ajax 存在以下问题:

  • 代码冗长,重复性高
  • 兼容性问题(特别是老版本 IE)
  • 缺乏统一的错误处理
  • 不支持 Promise,回调地狱问题

4. Ajax 封装实现

4.1 基础封装

/**
 * 基础 Ajax 封装
 * @param {Object} options 配置选项
 */
function ajax(options = {}) {
    const {
        url,
        method = 'GET',
        data = null,
        headers = {},
        timeout = 10000,
        success,
        error
    } = options;

    const xhr = new XMLHttpRequest();

    // 处理 GET 请求的查询参数
    let requestUrl = url;
    if (method === 'GET' && data) {
        const params = new URLSearchParams(data).toString();
        requestUrl = `${url}?${params}`;
    }

    xhr.open(method, requestUrl, true);

    // 设置请求头
    xhr.setRequestHeader('Content-Type', 'application/json');
    Object.keys(headers).forEach(key => {
        xhr.setRequestHeader(key, headers[key]);
    });

    // 超时处理
    xhr.timeout = timeout;

    // 响应处理
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 200 && xhr.status < 300) {
                const response = {
                    data: parseResponse(xhr.responseText),
                    status: xhr.status,
                    statusText: xhr.statusText
                };
                success && success(response);
            } else {
                error && error(new Error(`请求失败: ${xhr.status}`));
            }
        }
    };

    xhr.ontimeout = function() {
        error && error(new Error('请求超时'));
    };

    xhr.onerror = function() {
        error && error(new Error('网络错误'));
    };

    // 发送请求
    xhr.send(method !== 'GET' ? JSON.stringify(data) : null);

    // 解析响应
    function parseResponse(response) {
        try {
            return JSON.parse(response);
        } catch {
            return response;
        }
    }
}

4.2 Promise 封装

/**
 * Promise 风格的 Ajax 封装
 */
function ajaxPromise(options = {}) {
    return new Promise((resolve, reject) => {
        const {
            url,
            method = 'GET',
            data = null,
            headers = {},
            timeout = 10000
        } = options;

        const xhr = new XMLHttpRequest();

        // 处理 GET 请求参数
        let requestUrl = url;
        if (method === 'GET' && data) {
            const params = new URLSearchParams(data).toString();
            requestUrl = `${url}?${params}`;
        }

        xhr.open(method, requestUrl, true);

        // 设置请求头
        Object.entries({
            'Content-Type': 'application/json',
            ...headers
        }).forEach(([key, value]) => {
            xhr.setRequestHeader(key, value);
        });

        xhr.timeout = timeout;

        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    const response = xhr.responseText 
                        ? JSON.parse(xhr.responseText) 
                        : xhr.responseText;
                    resolve({
                        data: response,
                        status: xhr.status,
                        statusText: xhr.statusText
                    });
                } catch (error) {
                    resolve({
                        data: xhr.responseText,
                        status: xhr.status,
                        statusText: xhr.statusText
                    });
                }
            } else {
                reject(new Error(`请求失败: ${xhr.status}`));
            }
        };

        xhr.ontimeout = function() {
            reject(new Error('请求超时'));
        };

        xhr.onerror = function() {
            reject(new Error('网络错误'));
        };

        // 发送请求
        const requestData = method !== 'GET' && data 
            ? JSON.stringify(data) 
            : null;
        xhr.send(requestData);
    });
}

4.3 高级封装(支持拦截器、取消请求等)

class Ajax {
    constructor(baseURL = '') {
        this.baseURL = baseURL;
        this.interceptors = {
            request: [],
            response: []
        };
        this.pendingRequests = new Map();
    }

    /**
     * 添加请求拦截器
     */
    addRequestInterceptor(interceptor) {
        this.interceptors.request.push(interceptor);
    }

    /**
     * 添加响应拦截器
     */
    addResponseInterceptor(interceptor) {
        this.interceptors.response.push(interceptor);
    }

    /**
     * 执行请求
     */
    request(config) {
        // 生成请求唯一标识
        const requestId = `${config.method}-${config.url}-${Date.now()}`;

        // 执行请求拦截器
        let processedConfig = { ...config };
        this.interceptors.request.forEach(interceptor => {
            processedConfig = interceptor(processedConfig);
        });

        // 创建取消令牌
        const controller = new AbortController();
        this.pendingRequests.set(requestId, controller);

        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            const url = this.baseURL + processedConfig.url;

            xhr.open(
                processedConfig.method || 'GET',
                url,
                true
            );

            // 设置请求头
            const headers = {
                'Content-Type': 'application/json',
                ...processedConfig.headers
            };
            Object.entries(headers).forEach(([key, value]) => {
                xhr.setRequestHeader(key, value);
            });

            // 超时设置
            xhr.timeout = processedConfig.timeout || 10000;

            // 设置中止信号
            if (controller.signal) {
                controller.signal.onabort = () => {
                    xhr.abort();
                    reject(new Error('请求已被取消'));
                };
            }

            xhr.onload = () => {
                this.pendingRequests.delete(requestId);

                let response = {
                    data: this.parseResponse(xhr.responseText),
                    status: xhr.status,
                    statusText: xhr.statusText,
                    headers: this.parseHeaders(xhr.getAllResponseHeaders()),
                    config: processedConfig
                };

                // 执行响应拦截器
                this.interceptors.response.forEach(interceptor => {
                    response = interceptor(response);
                });

                if (xhr.status >= 200 && xhr.status < 300) {
                    resolve(response);
                } else {
                    reject(this.createError('请求失败', processedConfig, xhr, response));
                }
            };

            xhr.ontimeout = () => {
                this.pendingRequests.delete(requestId);
                reject(this.createError('请求超时', processedConfig, xhr));
            };

            xhr.onerror = () => {
                this.pendingRequests.delete(requestId);
                reject(this.createError('网络错误', processedConfig, xhr));
            };

            // 发送数据
            const data = processedConfig.method !== 'GET' && processedConfig.data
                ? JSON.stringify(processedConfig.data)
                : null;
            xhr.send(data);
        });
    }

    /**
     * 取消请求
     */
    cancelRequest(requestId) {
        const controller = this.pendingRequests.get(requestId);
        if (controller) {
            controller.abort();
            this.pendingRequests.delete(requestId);
        }
    }

    /**
     * 取消所有请求
     */
    cancelAllRequests() {
        this.pendingRequests.forEach(controller => controller.abort());
        this.pendingRequests.clear();
    }

    /**
     * 解析响应
     */
    parseResponse(response) {
        try {
            return JSON.parse(response);
        } catch {
            return response;
        }
    }

    /**
     * 解析响应头
     */
    parseHeaders(headersString) {
        const headers = {};
        if (headersString) {
            headersString.split('\r\n').forEach(line => {
                const parts = line.split(': ');
                if (parts.length === 2) {
                    headers[parts[0]] = parts[1];
                }
            });
        }
        return headers;
    }

    /**
     * 创建错误对象
     */
    createError(message, config, xhr, response) {
        const error = new Error(message);
        error.config = config;
        error.code = xhr.status;
        error.request = xhr;
        error.response = response;
        return error;
    }

    /**
     * 快捷方法
     */
    get(url, config = {}) {
        return this.request({ ...config, method: 'GET', url });
    }

    post(url, data = {}, config = {}) {
        return this.request({ ...config, method: 'POST', url, data });
    }

    put(url, data = {}, config = {}) {
        return this.request({ ...config, method: 'PUT', url, data });
    }

    delete(url, config = {}) {
        return this.request({ ...config, method: 'DELETE', url });
    }
}

5. 使用示例

5.1 基本使用

// 创建实例
const http = new Ajax('https://api.example.com');

// 添加拦截器
http.addRequestInterceptor(config => {
    // 添加认证 token
    config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
    return config;
});

http.addResponseInterceptor(response => {
    // 统一处理响应
    if (response.status === 401) {
        // 跳转到登录页
        window.location.href = '/login';
    }
    return response;
});

// 发送请求
http.get('/users')
    .then(response => {
        console.log('数据:', response.data);
    })
    .catch(error => {
        console.error('错误:', error);
    });

// POST 请求
http.post('/users', {
    name: '张三',
    age: 25
})
    .then(response => {
        console.log('创建成功');
    });

5.2 取消请求

const requestId = 'user-list-request';

// 发送请求
http.request({
    url: '/users',
    method: 'GET',
    requestId: requestId
})
    .then(response => {
        console.log(response.data);
    })
    .catch(error => {
        if (error.message === '请求已被取消') {
            console.log('请求已取消');
        }
    });

// 在需要时取消请求
setTimeout(() => {
    http.cancelRequest(requestId);
}, 1000);

6. 与现有库的比较

6.1 vs jQuery.ajax

// jQuery
$.ajax({
    url: '/api/data',
    method: 'GET',
    success: function(data) {
        console.log(data);
    }
});

// 自定义封装
ajax({
    url: '/api/data',
    method: 'GET',
    success: function(response) {
        console.log(response.data);
    }
});

6.2 vs axios

// axios
axios.get('/api/data')
    .then(response => {
        console.log(response.data);
    });

// 自定义封装
http.get('/api/data')
    .then(response => {
        console.log(response.data);
    });

7. 最佳实践建议

统一错误处理:封装统一的错误处理逻辑 请求拦截器:用于添加认证信息、设置公共参数 响应拦截器:用于统一处理响应数据、错误码 取消请求:在组件卸载时取消未完成的请求 防抖节流:频繁请求时使用防抖或节流 类型定义:使用 TypeScript 增强类型安全 请求缓存:对相同请求进行缓存优化

8. TypeScript 版本

interface AjaxConfig {
    url: string;
    method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
    data?: any;
    headers?: Record<string, string>;
    timeout?: number;
}

interface AjaxResponse<T = any> {
    data: T;
    status: number;
    statusText: string;
    headers: Record<string, string>;
    config: AjaxConfig;
}

class Ajax {
    // ... TypeScript 实现
}

总结

封装 Ajax 可以显著提升代码的可维护性和开发效率。根据项目需求,可以选择不同的封装策略。对于简单项目,基础封装即可满足需求;对于复杂的企业级应用,建议使用更完整的封装方案,包含拦截器、取消请求、错误处理等高级功能。

在实际开发中,也可以考虑使用成熟的第三方库如 axios,它们经过了充分测试,功能完善,社区支持好。但如果需要特殊定制或有特殊需求,自己封装也是一个不错的选择。

相关帖子
海安市个体工商户注销代办-工商注册服务,专业代办服务,收费透明
海安市个体工商户注销代办-工商注册服务,专业代办服务,收费透明
2026年全年法定节假日放假时间完整表
2026年全年法定节假日放假时间完整表
柚花开放特性研究:夜间绽放的白色花朵如何吸引传粉者
柚花开放特性研究:夜间绽放的白色花朵如何吸引传粉者
海安市企业网站开发设计@购物网站设计开发,专业开发团队
海安市企业网站开发设计@购物网站设计开发,专业开发团队
海安市食品卫生许可证办理电话|公司注册代办,欢迎电话咨询,不成功不收费
海安市食品卫生许可证办理电话|公司注册代办,欢迎电话咨询,不成功不收费
新乡市工商异常解除-中小微企业注册,快速办理
新乡市工商异常解除-中小微企业注册,快速办理
宜宾市网站设计公司@做网站,多年专业建站经验
宜宾市网站设计公司@做网站,多年专业建站经验
未来的城市规划与建筑设计,可能会如何从源头减少交通与生活噪音的影响?
未来的城市规划与建筑设计,可能会如何从源头减少交通与生活噪音的影响?
如何通过分析满意度回访的结果数据,来发现服务流程中的潜在问题?
如何通过分析满意度回访的结果数据,来发现服务流程中的潜在问题?
襄阳市危险化学品经营许可证代办服务-网络公司注册,代办经验丰富,快速办理
襄阳市危险化学品经营许可证代办服务-网络公司注册,代办经验丰富,快速办理
从长期主义看,2026年的自媒体创作者应如何定义自己的成功?
从长期主义看,2026年的自媒体创作者应如何定义自己的成功?
保山市食品经营许可证办理|工商注册公司,正规代办公司,收费合理
保山市食品经营许可证办理|工商注册公司,正规代办公司,收费合理
三明市工商注销代办电话-工商注册代办,全程代办,收费透明
三明市工商注销代办电话-工商注册代办,全程代办,收费透明
芜湖市食品经营许可证代办电话-股份有限公司注册,服务好,费用低,诚信为本!
芜湖市食品经营许可证代办电话-股份有限公司注册,服务好,费用低,诚信为本!
灵活就业人员缴纳社保,未来退休时养老金到底是怎么计算的?
灵活就业人员缴纳社保,未来退休时养老金到底是怎么计算的?
镇海区网站建设本地公司@AI数字人制作短视频,提供一站式建站服务
镇海区网站建设本地公司@AI数字人制作短视频,提供一站式建站服务
大风、暴雨、暴雪等不同颜色的预警,对应的停工停课标准是怎样的?
大风、暴雨、暴雪等不同颜色的预警,对应的停工停课标准是怎样的?
Windows Server 2025 安装AD CS角色和颁发证书
Windows Server 2025 安装AD CS角色和颁发证书
普洱市商标注册|股份有限公司注册,正规代办公司
普洱市商标注册|股份有限公司注册,正规代办公司
成都市食品卫生许可证办理电话-个体户注册,不成功不收费,专业代办
成都市食品卫生许可证办理电话-个体户注册,不成功不收费,专业代办