直接代理 window 或 globalThis 无法可靠埋点,因多数全局 API 不可配置/不可写,且 window 在 iframe 等环境中非可代理对象;应劫持 fetch 和 XMLHttpRequest.prototype.send,并逐个重定义 console 方法。

直接代理 window 或 globalThis 无法实现可靠埋点,会静默失效或抛出 TypeError;必须绕过全局对象,精准劫持函数调用入口。
为什么 new Proxy(window, {}) 会失败
多数全局 API(如 fetch、XMLHttpRequest.prototype.send、console.error)在浏览器中是不可配置(configurable: false)、不可写(writable: false)的内置属性。Proxy 只能代理标准对象,而 window 在 iframe、Web Worker 等上下文中甚至不是可代理对象——尝试代理会触发 TypeError: Cannot create proxy with a non-object as target,或干脆静默跳过。
更关键的是:console 是 host object,其方法绑定到特定 execution context,无法通过 Proxy 拦截行为;setTimeout 等定时器若强行代理,极易引发递归调用和性能雪崩。
优先劫持 fetch 和 XMLHttpRequest.prototype.send
这两者覆盖 90% 以上的网络请求日志需求,且入口明确、行为可控。劫持时需注意:
立即学习“前端免费学习笔记(深入)”;
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
- 用
Object.defineProperty替换原生fetch,避免直接赋值导致 descriptor 被忽略 - 新函数中必须用
originalFetch.apply(this, args)调用原始方法,保证this绑定和参数展开正确(即使fetch不依赖this,统一模式可避免后续扩展出错) - 对
XMLHttpRequest.prototype.send劫持时,需在实例上保存 trace 上下文(如xhr.__traceId),因为该方法无返回值,无法像fetch那样链式注入 - 务必在所有业务代码执行前完成劫持(如放在
<script>最顶部或模块入口处),否则已有引用将绕过拦截
逐个重定义 console 方法,不能靠 Proxy
console 的每个方法(log、error、warn)都必须单独重赋值,否则无法捕获调用。示例:
const originalConsoleError = console.error;
console.error = function(...args) {
// 上报错误日志
report({ type: 'console.error', args, timestamp: Date.now() });
// 保持原始行为
originalConsoleError.apply(console, args);
};
注意点:
- 不要用箭头函数重写,否则
this指向丢失,apply失效 - 必须保留
console对象本身,仅替换其方法;否则某些框架(如 React DevTools)会检测并报错 - 若需兼容旧版 IE,需额外处理
console不存在的情况,但现代埋点方案通常不考虑
容易被忽略的边界:iframe 与第三方 script
主页面劫持无法影响 iframe 内的 window 或第三方脚本动态创建的 XMLHttpRequest 实例。真正健壮的方案必须:
- 对每个新创建的 iframe,监听
load后主动向其contentWindow注入相同劫持逻辑(需同源) - 不依赖全局变量传入 SDK,改用立即执行函数包裹劫持逻辑,避免污染环境
- 第三方 script 若使用
eval或Function构造器动态生成请求代码,可能绕过原型劫持——此时需配合document.createElement('script')拦截或 CSPscript-src配合审计
最棘手的其实是异步加载的模块(如 import() 后的代码),它们的执行上下文独立,劫持逻辑若未提前注入,就会漏掉。这决定了“零侵入”永远是相对的——你总得在某个时机、以某种方式把探针放进去。

















