我们知道,setTimeout/setInterval 是 JavaScript 语言下的两门利器。有时候控件没反应了,代码外层包装一下 setTimeout 就可以了。JavaScript 是单线程的环境,setTimeout 的作用是把包装的代码塞入队列,而不是立刻执行。这一招对付莫名其妙的渲染问题非常有效。使用上, setTimeout/setInterval 要求第一个参数类型为 String 或 Function。遇到 Function,自然涉及传参的问题。就像 event handler 那样,仿佛不容易给函数送入参数。最简单的解决办法,就是外加多一层
function(){ 调用原函数(参数1、 参数2);}。这样子写法可以则可以,但写法上就显得”不那么美观“了。于是,我较常用的办法,就是尽量不设计参数的传入,而是作用域 this 身上绑定成员的做法。如果实在需要传参的话,使用 function.delegate 方法预定义参数列表(参考http://blog.csdn.net/zhangxin09/article/details/8508128
Function.prototype.delegate 部分)。这样代码看上去会优雅一点(尽管内部仍然是包装多次了 function)。
后来,一次偶然的情况,我留意到, 竟可以对 setTimeout/setInterval 第一个参数传参!具体就是新版的浏览器中,已经考虑了 setTimeout/setInterval 如何方便传参的问题。于是,无须上述提过的办法,一般新版浏览器中的 setTimeout/setInterval 在其第三个参数开始,便可以定义 setTimeout/setInterval 是第一个参数 Function 其参数列表。
能够利用原生的处理固然好,而且该功能在主流浏览器上得到支持。
然而遗憾的是,我们网民使用率较高的 Internet Explorer 浏览器却没有及时更新,以至为了setTimeout/setInterval 传参的问题,我们不得不写一个兼容的补丁函数。在你的 JS 库中加入以下代码,便可针对 IE 加入 setTimeout/setInterval 直接传参的功能。
/*\ |*| |*| IE-specific polyfill which enables the passage of arbitrary arguments to the |*| callback functions of javascript timers (HTML5 standard syntax). |*| |*| https://developer.mozilla.org/en-US/docs/DOM/window.setInterval |*| |*| Syntax: |*| var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]); |*| var timeoutID = window.setTimeout(code, delay); |*| var intervalID = window.setInterval(func, delay[, param1, param2, ...]); |*| var intervalID = window.setInterval(code, delay); |*| \*/ ;(function(){ if (document.all && !window.setTimeout.isPolyfill) { var __nativeST__ = window.setTimeout; window.setTimeout = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) { var aArgs = Array.prototype.slice.call(arguments, 2); return __nativeST__(vCallback instanceof Function ? function () { vCallback.apply(null, aArgs); } : vCallback, nDelay); }; window.setTimeout.isPolyfill = true; } if (document.all && !window.setInterval.isPolyfill) { var __nativeSI__ = window.setInterval; window.setInterval = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) { var aArgs = Array.prototype.slice.call(arguments, 2); return __nativeSI__(vCallback instanceof Function ? function () { vCallback.apply(null, aArgs); } : vCallback, nDelay); }; window.setInterval.isPolyfill = true; } })();
以上代码来自 Mozilla 的技术文档。怎么样,咋一眼看到的注释挺帅的~是吧——老外就喜欢琢磨些 Cool 的东西。至于代码具体原理,小弟这里就不详述了,应该比较简单。主要就是记下原生函数引用供后来覆盖的同名函数再通过 apply() 调用,这时,参数已经处理过了。对外部的接口做到兼容一致。同时还标记 isPolyfill 为 true 表示降级处理。这一招也是本人在自己的 JS 补丁库经常使用的手段,去解决兼容性的问题。
江山代有才人出。不曾想,国内又有一高手,释出更精简的代码,可把 setTimeout/ setInterval 两者冗余部分的代码合二为一,思路巧妙。请见下面代码。
if(!+[1,]) { (function(f){ window.setTimeout =f(window.setTimeout); window.setInterval =f(window.setInterval); })(function(f){ return function(c,t){ var a=[].slice.call(arguments,2); return f(function(){ c.apply(this,a)},t) } }); }
第一行判断是否 IE 浏览器就很有意思了。然后因为匿名函数传入了一个也是 Function 类型的参数,因此其运行的顺序是颠倒的,先执行 f()。fn() 的过程中,不仅覆盖原系统的 setTimeout/ setInterval,而且还巧妙地利用闭包特性把原系统的 setTimeout / setInterval 记忆在参数 f 中,相当于加了一层壳。例如 setTimeout,现阶段它等价于:
window.setTimeout = function(c,t){ var a=[].slice.call(arguments,2); return f(function(){ c.apply(this,a)},t) } }
好了,在这里处理参数列表就方便多了。arguments 第三个元素开始才是欲执行函数的参数列表,先通过 [].slice.call 获取回来,保存在 a 变量中。然后 f 才是真正的原生 setTimeout。c 是延时执行体,不能直接传入。因为我们的目的是对 c 传参数。这样也好办,就是 apply 一下就可以了。然后 t 就是计时器的毫秒数。
后来有网友反映不能传 string 类型的参数。我觉得,如果可以统一规范,可以不传 String 就不传 String,约束大家都使用 Function。