起因
最近公司需要增加前端JS探针对setTimeOut方法的监控(不能更改现有代码),其中需要将部分定义时的变量值保持到方法执行时也存在,让在回调方法中的重写的Ajax方法能获取这些变量。
开搞
不就是重写嘛,搞搞搞。1
2
3
4
5
6
7
8
9
10
11
12
13var cache_setTimeout = setTimeout;
window.setTimeout = function (vCallback, nDelay) {
var click_id = xxxx.clickId;
if (!click_id) return cache_setTimeout(vCallback, nDelay);
var old_handle = typeof vCallback === "string" ? function () { eval(vCallback); } : vCallback;
var new_handle = function () {
var window_temp_click = Number(xxxx.clickId);
xxxx.clickId = click_id;
try { old_handle && old_handle.apply(this, arguments); }
finally { xxxx.clickId = window_temp_click; }
};
return cache_setTimeout(new_handle, nDelay)
};
完成,运行。然后我就傻了~ IE8没有效果
IE8下的神奇操作
于是就开始了面向Google编程, 找到了一篇文章1。好家伙🙃.IE8的setTimeout与window.setTimeout不是同一个东西
Initially, the property setTimeout exists on the prototype of window, not on window itself. So, when you ask for window.setTimeout, it actually traverses one step on the prototype chain to resolve the reference. Similarly, when you ask for setTimeout, it traverses down the scope chain, then to window, then down the prototype chain to resolve the reference.
I suspect that IE has a built-in optimization where it automatically caches the resolution of implied globals that are discovered all the way down on the prototype of the global object. It would have good reason to do so, as these are commonly requested references, and traversing that chain is costly. However, it must be setting this reference as read-only, since it’s just a caching optimization. This has the unfortunate side-effect of causing an exception to be thrown when attempting to assign to the reference by using it as an lvalue. The only way to kill this new reference is by using var in the global scope, but this puts us in the hoisting problem. What to do?
最终的解决方案
在js文件头部定义一下,然后再重写1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var cache_setTimeout = setTimeout;
eval("var setTimeout");
// ...原文件肯定不止这些
window.setTimeout = function (vCallback, nDelay) {
var click_id = xxxx.clickId;
if (!click_id) return cache_setTimeout(vCallback, nDelay);
var old_handle = typeof vCallback === "string" ? function () { eval(vCallback); } : vCallback;
var new_handle = function () {
var window_temp_click = Number(xxxx.clickId);
xxxx.clickId = click_id;
try { old_handle && old_handle.apply(this, arguments); }
finally { xxxx.clickId = window_temp_click; }
};
return cache_setTimeout(new_handle, nDelay)
};
// ...原文件肯定不止这些
参考链接
1. Replacing setTimeout
Globally ↩