1. 程式人生 > >JavaScript: 再論setTimeout、setInterval。其第三個引數和this的討論,超時巢狀和記憶體洩漏

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