你真的知道setTimeout是如何運行的嗎

你們看下以下代碼,猜猜執行結果:
var start = new Date; setTimeout(function(){ console.log('時間流逝了:'+(new Date - start)+'毫秒'); }, 200); while (new Date - start < 1000) {} console.log(1); function doSoming(){ setTimeout(function(){ console.log('時間又流逝了:'+(new Date - start)+'毫秒'); },10); } doSoming(); while (new Date - start < 2000) {} console.log(2);
結果是:
約1秒後輸出:1,
再過約1秒後輸出:2,
接着才當即輸出:時間流逝了: 2002 毫秒
最後輸出:時間又流逝了: 2003 毫秒
 
您猜對了沒?
 
這裏經過setTimeout來延遲執行的函數都被推到最後才執行了;
 
原理以下:
        在現有瀏覽器環境中,Javascript執行引擎是單線程的,主線程的語句和方法,會阻塞定時任務的運行,在Javascript執行引擎以外,存在一個任務隊列,當在代碼中調用setTimeout()方法時,註冊的延時方法會掛到瀏覽器內核其餘模塊處理,當延時方法到達觸發條件,即到達設置的延時時間時,該模塊再將要執行的方法添加至該模塊的任務隊列中。這一過程與執行引擎主線程獨立,執行引擎在主線程方法執行完畢,到達空閒狀態時,纔會從該模塊的任務隊列中順序提取任務來執行,這期間的時間,可能大於註冊任務時設置的延時時間;
        瀏覽器在空閒狀態下,會不斷的嘗試從模塊的任務隊列中提取任務,這稱爲事件循環模型;
        
        再回頭看下前面的代碼,第二個setTimeout()的延遲方法的延遲時間是10毫秒,比第一個要早觸發啊!爲何執行結果卻在後面?由於它被以前的代碼阻塞了約1000.5~1001毫秒了(視瀏覽器的處理速度),等他掛處處理模塊,等到觸發時間添加進任務隊列時,第一個setTimeout()的延遲方法早就被添加到模塊的任務隊了,而引擎主線程是按順序提取得,因此,你應該懂了吧?
        如今,若是把上面的while (new Date - start < 1000) {}改爲while (new Date - start < 189) {}或者是while (new Date - start < 190) {},結果又是什麼?我就很少說了!各刷新瀏覽器20遍,本身看結果吧!
 
而setInterval()方法和setTimeout()地位是相同的,調用setInterval()方法時,註冊的延時方法掛到模塊處理,每當觸發時間到達,就往任務隊列添加一次要執行的方法;
 
下面來具體看看setTimeout的語法:
var timeID = window.setTimeout(func,delay,[param1,param2,...]);
var timeID = window.setTimeout(code,delay);
        setTimeout和setInterval是Window對象的方法(可省略window),第二個以後的可選參數(IE9及舊版不支持)是傳遞給func的參數,每次調用他們時都會返回一個數字ID(在瀏覽器中打印出來就只是個數字,而本人在webstorm中打印出來發現它實際是一個對象,有不少個屬性),這個ID保持着它對應的setTimeout或setInterval的相關信息,主要用來在中模塊中和任務隊列中清除(或關閉)掉它們(用方法clearTimeout(ID)和clearInterval(ID))。
        若是你須要向你的回調函數內傳遞一個參數如下是兼容IE的寫法
if (document.all && !window.setTimeout.isPolyfill) { var __nativeST__ = window.setTimeout; window.setTimeout = function (vCallback, nDelay, param1, param2,param3) { var aArgs = Array.prototype.slice.call(arguments, 2); return __nativeST__(vCallback instanceof Function ? function () { vCallback.apply(null, aArgs); } : vCallback, nDelay); }; window.setTimeout.isPolyfill = true; }
一個常見的錯誤出如今循環中使用閉包
for(var i =0; i <10; i++){ setTimeout(function(){ console.log(i); },1000); }
上面的代碼只會輸出數字 10 十次。爲何?閉包!
當 console.log 被調用的時候,雖然匿名函數保持對外部變量 i 的引用,但此時 for循環已經結束, i 的值被修改爲了 10.
爲了獲得想要的結果,須要在每次循環中建立變量 i 的拷貝。
爲了正確的得到循環序號,最好使用 匿名包裹器(其實就是咱們一般說的自執行匿名函數)。
for(var i =0; i <10; i++){ (function(e){ setTimeout(function(){ console.log(e); },1000); })(i); }
外部的匿名函數會當即執行,並把 i 做爲它的參數,此時函數內 e 變量就擁有了 i 的一個拷貝。
當傳遞給 setTimeout 的匿名函數執行時,它就擁有了對 e 的引用,而這個值是不會被循環改變的。
有另外一個方法完成一樣的工做;那就是從匿名包裝器中返回一個函數。這和上面的代碼效果同樣。
for(var i =0; i <10; i++){ setTimeout((function(e){ return function(){ console.log(e); } })(i),1000) }
還有一個重要應用:函數節流(throttle)與函數去抖(debounce)
請看我從網上收集的一些資料:
 
參考連接:
相關文章
相關標籤/搜索