JavaScript: 再論setTimeout、setInterval。其第三個引數和this的討論,超時巢狀和記憶體洩漏
最近用setTimeout、setInterval,因為要傳入的函式要用到this,所以深入瞭解了一番!
setTimeout和setInterval函式的第三個引數本來只是定義語言型別,後來在非IE瀏覽器下支援傳遞引數,並且在不同瀏覽器下支援的不同。
原來的setTimeout函式定義:
var timeoutID = window.setTimeout(func, delay[, lang]);
在Chrome和FF下定義被修改:
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
var timeoutID = window.setTimeout(code, delay);
IE:不支援第三個引數。
Chrome:接受的引數=傳遞的引數個數。
FF:接受的引數=傳遞的引數個數+1
具體可參看:https://developer.mozilla.org/zh-CN/docs/DOM/window.setTimeout
建議看了上址,再看本文,效果更好!
程式設計師是看Code的,貼上一段詳細註釋過的精巧程式碼:
(function (w) { //ie傳入第三個引數 if (! + [1, ]) { //除IE外,!+[1,]都是返回false (function (overrideFn) { //overrideFn應改為wrapCall w.setTimeout = overrideFn(w.setTimeout); w.setInterval = overrideFn(w.setInterval); })(function (originalFn) {//包裝函式,僅供上二行呼叫,只一個引數一個類setInterval返回函式 originalFn.isPolyfill = true;//fix ie9-- //注意return替換函式,而非呼叫返回值。返回真正要被替換的setTimeout,setInterval return function (code, delay) {//這才是真正的overrideFn var me = this,//fix: 啟用code所在的this作用域 args = Array.prototype.slice.call(arguments, 2); return originalFn(function () {//這裡才呼叫原API if (typeof code == 'string') { eval(code); } else { code.apply(me, args); } }, delay); } }) } })(window);
上段程式碼修改自:http://mao.li/javascript/javascript-settimeout-params/
很簡練,貓也是自MDN修改而來!我註釋得很詳細,只要理解了匿名函式的呼叫就通了
現在可以測試一下:
myArray = ["zero", "one", "two"]; myArray.myMethod = function (sProperty) { alert(arguments.length > 0 ? this[sProperty] : this); }; setTimeout.call(myArray, myArray.myMethod, 1000); setTimeout.call(myArray, myArray.myMethod, 1500, 2);
注意二點:還是得用call或apply;傳給setTimeout的回撥如果是字串,則第三引數沒有意義!
上面程式碼的另外一點不足之處,就是原setTimeout的“指標”沒有儲存到return function (code, delay) {//這才是真正的overrideFn
這行程式碼後的資料中,不過考慮JS也不是原生支援OO,這點缺憾還是可以接受的!
現在,我要重點說說setTimeout的超時呼叫,特別是在迴圈中
while (!flag) {
//等待非同步完成==>程式碼意圖:每30毫秒輪詢非同步完成標誌,未完成則等待,讓出執行權響應使用者事件,意圖實現類似sleep效果
setTimeout(null, 30);
}
上面程式碼看上去好像沒問題,每30毫秒輪詢非同步完成標誌~但是Js是單執行緒的
所以,這就是段死迴圈,不停的建立超時計時器,其它程式碼沒法變更完成標誌!一二分鐘後就掛了!解決方法,就是回撥!
可以用定時器setInterval在回撥函式中輪詢非同步完成標誌。但最好的辦法還是在非同步操作中設定好回撥!JS也就這點令人煩,有時大量的回撥巢狀三四層,這樣OO程式碼實現就有點煩瑣了!
最後點一下,JS引擎單執行緒,佇列式執行:
setTimeout(fn, 0);//fn不會馬上執行,而是得先前的執行佇列完成才能執行
HTML5中規定:setTimeout最少超時4ms
參考資料:
http://mao.li/javascript/javascript-settimeout-params/
https://developer.mozilla.org/zh-CN/docs/DOM/window.setTimeout