
本文讲解为何直接修改 EventSource.prototype.onmessage 会导致 “Illegal invocation” 错误,并提供一种兼容、可靠的方式(使用 addEventListener)来全局监听所有 SSE 的 message 事件,同时不影响原有业务逻辑。
本文讲解为何直接修改 `eventsource.prototype.onmessage` 会导致 “illegal invocation” 错误,并提供一种兼容、可靠的方式(使用 `addeventlistener`)来全局监听所有 sse 的 `message` 事件,同时不影响原有业务逻辑。
在浏览器扩展或调试工具中,我们常希望无侵入地监控 Server-Sent Events(SSE)的接收过程,例如对每个 message 事件执行 console.log。一个直观的想法是“重写 EventSource 构造函数”,并在实例创建后劫持其 onmessage 属性。但以下写法是错误且危险的:
Object.getPrototypeOf(ori).onmessage = function (event) {
console.log(event);
ori.onmessage(event); // ❌ 非法调用:this 指向错误
};该代码试图将自定义处理逻辑赋值给 EventSource.prototype.onmessage —— 而 onmessage 是一个访问器属性(setter),其内部实现依赖于 this 必须是一个合法的 EventSource 实例。当在原型对象上直接赋值时(即 EventSource.prototype.onmessage = ...),this 指向的是 EventSource.prototype,而非具体实例,因此触发 TypeError: Illegal invocation。
✅ 正确做法是:在每个新创建的 EventSource 实例上,使用标准事件 API 添加监听器,既符合规范,又完全隔离于用户代码的 onmessage 或 addEventListener('message', ...) 调用:
const originalEventSource = window.EventSource;
window.EventSource = function(url, options) {
// 调用原生构造函数创建实例(注意:必须用 new originalEventSource(...))
const instance = new originalEventSource(url, options);
// 安全添加全局监听:不影响用户后续对 onmessage 的赋值或 addEventListener 的使用
instance.addEventListener('message', (event) => {
console.debug('[SSE Intercepted]', {
url,
data: event.data,
origin: event.origin,
time: new Date().toISOString()
});
});
return instance;
};
// ✅ 用户代码完全无需修改:
if (typeof EventSource !== 'undefined') {
const source = new EventSource('/api/events');
source.onmessage = (e) => {
document.getElementById('result').innerHTML += e.data + '<br>';
};
}⚠️ 注意事项:
-
不要覆盖
instance.onmessage:用户可能通过source.onmessage = handler设置回调,若你覆写它,会破坏原有逻辑; -
避免重复监听:本方案使用
addEventListener,天然支持多监听器,与用户调用互不干扰; -
兼容性保障:
addEventListener在所有支持 SSE 的浏览器中均可用(Chrome 6+、Firefox 6+、Safari 5.1+、Edge 12+); -
扩展场景建议:如需拦截
error或open事件,同样应使用addEventListener,而非篡改对应 setter。
总结:劫持内置 Web API 时,应优先选择标准化、基于实例的事件注册方式(addEventListener),而非尝试修改原型上的访问器属性——这既是规范要求,也是避免 Illegal invocation 的根本解法。

















