為什么要用 setTimeout 模擬 setInterval ?

JS 事件循環(huán)之宏任務(wù)和微任務(wù)中講到過,setInterval 是一個宏任務(wù)。

用多了你就會發(fā)現(xiàn)它并不是準(zhǔn)確無誤,極端情況下還會出現(xiàn)一些令人費解的問題。

下面我們一一羅列..

推入任務(wù)隊列后的時間不準(zhǔn)確

定時器代碼:

setInterval(fn(), N);

上面這句代碼的意思其實是fn()將會在 N 秒之后被推入任務(wù)隊列氛改。

所以,在 setInterval 被推入任務(wù)隊列時颜阐,如果在它前面有很多任務(wù)或者某個任務(wù)等待時間較長比如網(wǎng)絡(luò)請求等平窘,那么這個定時器的執(zhí)行時間和我們預(yù)定它執(zhí)行的時間可能并不一致。

比如:

let startTime = new Date().getTime();
let count = 0;
//耗時任務(wù)
setInterval(function() {
  let i = 0;
  while (i++ < 1000000000);
}, 0);
setInterval(function() {
  count++;
  console.log(
    "與原設(shè)定的間隔時差了:",
    new Date().getTime() - (startTime + count * 1000),
    "毫秒"
  );
}, 1000);
// 輸出:
// 與原設(shè)定的間隔時差了: 699 毫秒
// 與原設(shè)定的間隔時差了: 771 毫秒
// 與原設(shè)定的間隔時差了: 887 毫秒
// 與原設(shè)定的間隔時差了: 981 毫秒
// 與原設(shè)定的間隔時差了: 1142 毫秒
// 與原設(shè)定的間隔時差了: 1822 毫秒
// 與原設(shè)定的間隔時差了: 1891 毫秒
// 與原設(shè)定的間隔時差了: 2001 毫秒
// 與原設(shè)定的間隔時差了: 2748 毫秒
// ...

可以看出來凳怨,相差的時間是越來越大的瑰艘,越來越不準(zhǔn)確。

函數(shù)操作耗時過長導(dǎo)致的不準(zhǔn)確

考慮極端情況肤舞,假如定時器里面的代碼需要進(jìn)行大量的計算(耗費時間較長)紫新,或者是 DOM 操作。這樣一來李剖,花的時間就比較長芒率,有可能前一次代碼還沒有執(zhí)行完,后一次代碼就被添加到隊列了篙顺。也會到時定時器變得不準(zhǔn)確偶芍,甚至出現(xiàn)同一時間執(zhí)行兩次的情況。

最常見的出現(xiàn)的就是德玫,當(dāng)我們需要使用 ajax 輪詢服務(wù)器是否有新數(shù)據(jù)時匪蟀,必定會有一些人會使用 setInterval,然而無論網(wǎng)絡(luò)狀況如何宰僧,它都會去一遍又一遍的發(fā)送請求材彪,最后的間隔時間可能和原定的時間有很大的出入。

// 做一個網(wǎng)絡(luò)輪詢,每一秒查詢一次數(shù)據(jù)段化。
let startTime = new Date().getTime();
let count = 0;

setInterval(() => {
    let i = 0;
    while (i++ < 10000000); // 假設(shè)的網(wǎng)絡(luò)延遲
    count++;
    console.log(
        "與原設(shè)定的間隔時差了:",
        new Date().getTime() - (startTime + count * 1000),
        "毫秒"
    );
}, 1000)
輸出:
// 與原設(shè)定的間隔時差了: 567 毫秒
// 與原設(shè)定的間隔時差了: 552 毫秒
// 與原設(shè)定的間隔時差了: 563 毫秒
// 與原設(shè)定的間隔時差了: 554 毫秒(2次)
// 與原設(shè)定的間隔時差了: 564 毫秒
// 與原設(shè)定的間隔時差了: 602 毫秒
// 與原設(shè)定的間隔時差了: 573 毫秒
// 與原設(shè)定的間隔時差了: 633 毫秒

setInterval 缺點 與 setTimeout 的不同

再次強(qiáng)調(diào)嘁捷,定時器指定的時間間隔,表示的是何時將定時器的代碼添加到消息隊列显熏,而不是何時執(zhí)行代碼雄嚣。所以真正何時執(zhí)行代碼的時間是不能保證的,取決于何時被主線程的事件循環(huán)取到喘蟆,并執(zhí)行现诀。

setInterval(function, N)
//即:每隔N秒把function事件推到消息隊列中
setinterval-1.png

上圖可見,setInterval 每隔 100ms 往隊列中添加一個事件履肃;100ms 后,添加 T1 定時器代碼至隊列中坐桩,主線程中還有任務(wù)在執(zhí)行尺棋,所以等待,some event 執(zhí)行結(jié)束后執(zhí)行 T1 定時器代碼绵跷;又過了 100ms膘螟,T2 定時器被添加到隊列中,主線程還在執(zhí)行 T1 代碼碾局,所以等待荆残;又過了 100ms,理論上又要往隊列里推一個定時器代碼净当,但由于此時 T2 還在隊列中内斯,所以 T3 不會被添加(T3 被跳過),結(jié)果就是此時被跳過像啼;這里我們可以看到俘闯,T1 定時器執(zhí)行結(jié)束后馬上執(zhí)行了 T2 代碼,所以并沒有達(dá)到定時器的效果忽冻。

綜上所述真朗,setInterval 有兩個缺點:

  • 使用 setInterval 時,某些間隔會被跳過僧诚;
  • 可能多個定時器會連續(xù)執(zhí)行遮婶;

可以這么理解:每個 setTimeout 產(chǎn)生的任務(wù)會直接 push 到任務(wù)隊列中;而 setInterval 在每次把任務(wù) push 到任務(wù)隊列前湖笨,都要進(jìn)行一下判斷(看上次的任務(wù)是否仍在隊列中旗扑,如果有則不添加,沒有則添加)赶么。

因而我們一般用 setTimeout 模擬 setInterval肩豁,來規(guī)避掉上面的缺點。

來看一個經(jīng)典的例子來說明他們的不同:

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

做過的朋友都知道:是一次輸出了 5 個 5;
那么問題來了:是每隔 1 秒輸出一個 5 ?還是一秒后立即輸出 5 個 5清钥?
答案是:一秒后立即輸出 5 個 5
因為 for 循環(huán)了五次琼锋,所以 setTimeout 被 5 次添加到時間循環(huán)中,等待一秒后全部執(zhí)行祟昭。

為什么是一秒后輸出了 5 個 5 呢缕坎?
簡單來說,因為 for 是主線程代碼篡悟,先執(zhí)行完了谜叹,才輪到執(zhí)行 setTimeout。

當(dāng)然為什么輸出不是 1 到 5搬葬,這個涉及到作用域的問題了荷腊,這里就不解釋了。

那如果換成 setInterval 呢急凰?

for (var i = 0; i < 5; i++) {
  setInterval(function() {
    console.log(i);
  }, 1000);
}

輸出什么女仰?
答案是:每 1 秒輸出 5 個 5。
為什么輸出 5 個 5抡锈?
是因為 setInterval 只在第 for 循環(huán)時被添加了疾忍,后面的并沒有添加,也就是之前說的床三,setInterval 在每次把任務(wù) push 到任務(wù)隊列前一罩,都要進(jìn)行一下判斷(看上次的任務(wù)是否仍在隊列中,如果有則不添加撇簿,沒有則添加)聂渊。

setTimeout 模擬 setInterval

綜上所述,在某些情況下四瘫,setInterval 并不是很準(zhǔn)確的歧沪。為了解決這些弊端,可以使用 settTimeout() 代替莲组。具體實現(xiàn)如下:

1.寫一個 interval 方法

let timer = null
interval(func, wait){
    let interv = function(){
        func.call(null);
        timer=setTimeout(interv, wait);
    };
    timer= setTimeout(interv, wait);
 },

2.和 setInterval() 一樣使用它

interval(function() {}, 20);

3.終止定時器

if (timer) {
  window.clearSetTimeout(timer);
  timer = null;
}

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锹杈,隨后出現(xiàn)的幾起案子撵孤,更是在濱河造成了極大的恐慌,老刑警劉巖竭望,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邪码,死亡現(xiàn)場離奇詭異,居然都是意外死亡咬清,警方通過查閱死者的電腦和手機(jī)闭专,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門奴潘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人影钉,你說我怎么就攤上這事画髓。” “怎么了平委?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵奈虾,是天一觀的道長。 經(jīng)常有香客問我廉赔,道長肉微,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任蜡塌,我火速辦了婚禮碉纳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘馏艾。我一直安慰自己村象,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布攒至。 她就那樣靜靜地躺著,像睡著了一般躁劣。 火紅的嫁衣襯著肌膚如雪迫吐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天账忘,我揣著相機(jī)與錄音志膀,去河邊找鬼。 笑死鳖擒,一個胖子當(dāng)著我的面吹牛溉浙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒋荚,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼戳稽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了期升?” 一聲冷哼從身側(cè)響起惊奇,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎播赁,沒想到半個月后颂郎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡容为,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年乓序,在試婚紗的時候發(fā)現(xiàn)自己被綠了寺酪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡替劈,死狀恐怖寄雀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抬纸,我是刑警寧澤咙俩,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站湿故,受9級特大地震影響阿趁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坛猪,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一脖阵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧墅茉,春花似錦命黔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洋机,卻和暖如春坠宴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绷旗。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工喜鼓, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衔肢。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓庄岖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親角骤。 傳聞我的和親對象是個殘疾皇子隅忿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容