在浏览器里”hook” 某个内置函数,本质就是——把原函数偷出来,用你自己的函数替换它。setInterval 是最常见的 hook 目标:调试疯狂轮询、监控页面定时器、干掉反调试轮询。
一句话原理
const _origin = window.setInterval;
window.setInterval = function (fn, delay, ...args) {
// 你的自定义逻辑
return _origin(fn, delay, ...args); // 回到原函数
};
_origin 是原函数引用,window.setInterval 是你替换后的新函数。之后所有 setInterval(...) 调用都会先经过你。
场景一:监控所有调用
(function () {
const _origin = window.setInterval;
window.setInterval = function (fn, delay, ...args) {
console.log("setInterval:", { delay, fn: fn.toString().slice(0, 80) });
return _origin(fn, delay, ...args);
};
})();
用途:找出页面里到底谁在挂定时器。
场景二:按条件拦截
只关心 1 秒轮询:
window.setInterval = function (fn, delay, ...args) {
if (delay === 1000) {
console.trace("1s interval:", fn);
}
return _origin(fn, delay, ...args);
};
console.trace 直接打出调用栈,能追到是哪段代码挂的。
场景三:阻止高频轮询
页面每 50ms 疯狂检查某个东西(典型反调试或广告轮询),CPU 直接吃满:
window.setInterval = function (fn, delay, ...args) {
if (delay < 500) {
console.log("阻止高频 interval:", delay);
return null; // 直接返回 null,不真的开定时器
}
return _origin(fn, delay, ...args);
};
场景四:记录所有 ID 方便统一清
(function () {
const _origin = window.setInterval;
const ids = new Set();
window.setInterval = function (fn, delay, ...args) {
const id = _origin(fn, delay, ...args);
ids.add(id);
return id;
};
window.__clearAll = () => {
ids.forEach(clearInterval);
ids.clear();
console.log("全部清除");
};
})();
在 devtools 里手动 __clearAll() 就能一键把当前页面所有定时器停了。
场景五:油猴脚本里 hook
必须 @run-at document-start——否则页面脚本先跑,等你 hook 上去已经晚了:
// ==UserScript==
// @name hook setInterval
// @match https://example.com/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function () {
const _origin = window.setInterval;
window.setInterval = function (fn, delay, ...args) {
console.log("intercepted:", delay);
return _origin.call(window, fn, delay, ...args);
};
})();
注意用 _origin.call(window, ...)——某些浏览器的 setInterval 严格要求 this === window,不 bind 会抛 Illegal invocation。
常见反 hook 手法
页面可能这样绕过你:
// 提前保存原始引用
const _real = window.setInterval.bind(window);
_real(fn, 100);
- 页面代码保存的是你的 hook 前的原函数,你就拦不到
- 或者 iframe 里跑,
window隔离 - 或者用
postMessage+ Worker,压根不走主线程setInterval
对策:
@run-at document-start越早越好- 同时 hook
setTimeout/requestAnimationFrame——很多站点会混着用 - iframe 里的定时器要另外注入
用 Proxy 更隐蔽
上面的写法暴露痕迹(window.setInterval.toString() 会变),要更隐蔽:
window.setInterval = new Proxy(window.setInterval, {
apply(target, thisArg, args) {
const [fn, delay] = args;
console.log("intercepted:", delay);
return Reflect.apply(target, thisArg, args);
},
});
Proxy 版本的 .toString() 还是原始的,页面用 toString().includes("[native code]") 检测更难发现。
一句话总结
hook = 存原函数引用 + 替换 window 属性。油猴里必须 run-at document-start,进阶用 Proxy 更隐蔽。同一套模板套 setTimeout / fetch / XMLHttpRequest 也照样能用。
