JavaScript 监听全部 Ajax 请求事件的方法
本文介绍如何在无法修改源码的情况下,通过重写 XMLHttpRequest 原型并配合 CustomEvent 自定义事件,实现原生 JavaScript 发起的所有 Ajax 请求的全局监听。文章对比了 jQuery 全局事件和 StackOverflow 上的常见错误方案,详细讲解了代码实现、事件对象差异处理以及针对 IE 浏览器的 Polyfill 兼容方案,确保在主流浏览器中稳定运行。
问题背景与方案选择
如果 Ajax 请求是通过 jQuery 的 $.ajax 发起的,通常可以直接利用 jQuery 提供的 Global Ajax Event Handlers 来监听相关事件。但在实际场景中,若请求是由原生 JavaScript 发起的,上述方法便失效了。
另一种思路是使用 Pub/Sub 模式,但由于无法修改发起请求的原始 JS 代码,因此无法在代码中插入 publish 逻辑。同理,jQuery 提供的 .bind 和 .trigger 方法也无法在此场景下使用。
最终方案决定采用直接 override XMLHttpRequest 的方式,并结合自定义事件来实现监听功能。
在 StackOverflow 上搜索时,发现有一个国外开发者提供的解决方案存在缺陷,具体问题分析如下。
常见错误方案分析
;(function () {
var open = window.XMLHttpRequest.prototype.open,
send = window.XMLHttpRequest.prototype.send,
onReadyStateChange;
function openReplacement(method, url, async, user, password) {
// some code
return open.apply(this, arguments);
}
function sendReplacement(data) {
// some code
if(this.onreadystatechange) this._onreadystatechange = this.onreadystatechange;
this.onreadystatechange = onReadyStateChangeReplacement;
return send.apply(this, arguments);
}
function onReadyStateChangeReplacement() {
// some code
if (this._onreadystatechange) return this._onreadystatechange.apply(this, arguments);
}
window.XMLHttpRequest.prototype.open = openReplacement;
window.XMLHttpRequest.prototype.send = sendReplacement;
})();
该方案存在几个主要问题:它无法监听所有的 XHR Events;readystatechange 事件仅在调用 send 方法之后才被监听,导致无法捕获 readyState = 1 时的事件。此外,如果在调用 send 方法后再设置 onreadystatechange 的回调函数,会导致 override 的代码被重复 override,从而无法达到预期效果。
那么,如何正确地 override XHR 呢?以下是具体的代码实现。
正确的 XHR 重写实现
;(function() {
function ajaxEventTrigger(event) {
var ajaxEvent = new CustomEvent(event, { detail: this });
window.dispatchEvent(ajaxEvent);
}
var oldXHR = window.XMLHttpRequest;
function newXHR() {
var realXHR = new oldXHR();
realXHR.addEventListener('abort', function () { ajaxEventTrigger.call(this, 'ajaxAbort'); }, false);
realXHR.addEventListener('error', function () { ajaxEventTrigger.call(this, 'ajaxError'); }, false);
realXHR.addEventListener('load', function () { ajaxEventTrigger.call(this, 'ajaxLoad'); }, false);
realXHR.addEventListener('loadstart', function () { ajaxEventTrigger.call(this, 'ajaxLoadStart'); }, false);
realXHR.addEventListener('progress', function () { ajaxEventTrigger.call(this, 'ajaxProgress'); }, false);
realXHR.addEventListener('timeout', function () { ajaxEventTrigger.call(this, 'ajaxTimeout'); }, false);
realXHR.addEventListener('loadend', function () { ajaxEventTrigger.call(this, 'ajaxLoadEnd'); }, false);
realXHR.addEventListener('readystatechange', function() { ajaxEventTrigger.call(this, 'ajaxReadyStateChange'); }, false);
return realXHR;
}
window.XMLHttpRequest = newXHR;
})();
通过上述代码,我们为 XHR 添加了自定义事件。具体的调用方式如下。
自定义事件调用示例
var xhr = new XMLHttpRequest();
window.addEventListener('ajaxReadyStateChange', function (e) {
console.log(e.detail); // XMLHttpRequest Object
});
window.addEventListener('ajaxAbort', function (e) {
console.log(e.detail.responseText); // XHR 返回的内容
});
xhr.open('GET', 'info.json');
xhr.send();
事件对象差异与注意事项
需要注意的是,正常的 readystatechange 等事件 handler 返回的 e 是 XMLHttpRequest 对象,但是自定义方法 ajaxReadyStateChange 等事件 handler 返回的 e 是 CustomEvent 对象,而 e.detail 才是真正的 XMLHttpRequest 对象。而获得 Ajax 请求返回内容的 e.responseText 也需要修改为 e.detail.responseText。
同时,addEventListener 方法必须挂载在 window 对象 上,而不能挂载在 XHR 实例上。
由于上述代码使用了 CustomEvent 构造函数,在现代浏览器中可以正常运行,但在 IE 浏览器中,包括 IE 11 也不支持该构造函数。因此,需要添加 Polyfill 进行兼容处理,代码调整如下。
IE 浏览器兼容性处理
;(function () {
if ( typeof window.CustomEvent === "function" ) return false;
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
;(function () {
function ajaxEventTrigger(event) {
var ajaxEvent = new CustomEvent(event, { detail: this });
window.dispatchEvent(ajaxEvent);
}
var oldXHR = window.XMLHttpRequest;
function newXHR() {
var realXHR = new oldXHR();
realXHR.addEventListener('abort', function () { ajaxEventTrigger.call(this, 'ajaxAbort'); }, false);
realXHR.addEventListener('error', function () { ajaxEventTrigger.call(this, 'ajaxError'); }, false);
realXHR.addEventListener('load', function () { ajaxEventTrigger.call(this, 'ajaxLoad'); }, false);
realXHR.addEventListener('loadstart', function () { ajaxEventTrigger.call(this, 'ajaxLoadStart'); }, false);
realXHR.addEventListener('progress', function () { ajaxEventTrigger.call(this, 'ajaxProgress'); }, false);
realXHR.addEventListener('timeout', function () { ajaxEventTrigger.call(this, 'ajaxTimeout'); }, false);
realXHR.addEventListener('loadend', function () { ajaxEventTrigger.call(this, 'ajaxLoadEnd'); }, false);
realXHR.addEventListener('readystatechange', function() { ajaxEventTrigger.call(this, 'ajaxReadyStateChange'); }, false);
return realXHR;
}
window.XMLHttpRequest = newXHR;
})();
经过上述处理,该方案即可在 IE 9+、Chrome 15+、FireFox 11+、Edge、Safari 6.1+ 以及 Opera 12.1+ 等浏览器中正常使用。以上就是本文的全部内容,希望对大家有所帮助。
JavaScript零基础入门:从环境搭建到运行第一段代码
2026-11-05 15:29:58
HTML链接中javascript:void(0)的作用及现代替代方案
2026-11-05 10:07:47
在线调试 JS 时怎么引入 jQuery 或 Vue 等第三方库
2026-11-05 08:53:56
原生JavaScript实现简易颜色选择器原理与代码
2026-11-03 23:22:19
JS原生表单序列化:FormData与URLSearchParams用法详解
2026-11-03 23:13:21
