由于 window.location
是一个特殊的只读对象,直接使用 Proxy
代理它会抛出错误(如 TypeError: Cannot create proxy with a non-configurable property
)。但我们可以通过 间接监听 的方式统计它的访问次数。以下是几种实现方案:
方法1:通过 Object.defineProperty
劫持部分属性
虽然无法代理整个 location
,但可以劫持它的某些属性(如 href
)的 getter/setter:
let accessCount = 0;
const originalHref = Object.getOwnPropertyDescriptor(window.location, 'href').get;
Object.defineProperty(window.location, 'href', {
get: function() {
accessCount++;
console.log(`location.href accessed (count: ${accessCount})`);
return originalHref.call(window.location);
},
configurable: true // 必须设置为 true
});
// 测试
console.log(window.location.href); // 触发计数
限制:只能监听已明确劫持的属性(如 href
、hash
等)。
方法2:包装 window.location
的引用
通过中间变量间接监听:
let accessCount = 0;
const wrappedLocation = new Proxy({}, {
get(target, prop) {
accessCount++;
console.log(`location.${prop} accessed (count: ${accessCount})`);
return window.location[prop]; // 返回真实值
}
});
// 重定向全局引用(需确保在目标代码之前执行)
window._originalLocation = window.location;
window.location = wrappedLocation;
// 测试
console.log(window.location.href); // 触发计数
注意:此方法可能被严格模式或某些安全策略阻止。
方法3:AST + Hook(推荐用于生产环境)
如果是自己的项目,可以通过 Babel/Webpack 插件在编译时注入统计代码:
// Babel插件示例:将所有 `window.location.x` 调用替换为统计函数
function replaceLocationAccess(babel) {
const { types: t } = babel;
return {
visitor: {
MemberExpression(path) {
if (path.get('object').matchesPattern('window.location')) {
path.replaceWith(
t.callExpression(
t.identifier('__countLocationAccess'),
[t.stringLiteral(path.node.property.name)]
)
);
}
}
}
};
}
// 全局统计函数
window.__countLocationAccess = (prop) => {
console.log(`location.${prop} accessed`);
return window.location[prop];
};
方法4:Chrome DevTools Overrides
保存 Overrides:
- Chrome DevTools → Sources → Overrides
- 复制页面脚本,手动添加统计代码后保存。
- Chrome DevTools → Sources → Overrides
示例修改:
// Find all location accesses in the original script and wrap them:
const originalCode = 'console.log(window.location.href);';
const instrumentedCode = originalCode.replace(
/(window\.location\.[a-zA-Z]+)/g,
'(window.__countAccess($1), $1)'
);
// Inject counter:
window.__accessCounts = {};
window.__countAccess = (prop) => {
window.__accessCounts[prop] = (window.__accessCounts[prop] || 0) + 1;
};
注意事项
- 执行时机:所有 Hook 必须在目标脚本访问
location
之前执行。 - 沙箱环境:某些网站使用 iframe sandboxing,会阻止属性修改。
- 备选方案:如果目标是调试而非生产代码,直接用 DevTools 的 Event Listener Breakpoints (监听
hashchange/popstate
)更可靠。
如果需要针对具体场景优化方案,可以提供更多细节(如是否允许修改原始代码、目标运行环境等)。
内容由零声教学AI助手提供,问题来源于学员提问