前言
之所以寫這篇文章是因為上周工作中使用setInterval輪詢請求接口時遇到了一些問題订框,如果哪里理解的不對請大家多多指教~
進入正題
setTimeout和setInteval是window對象上兩個主要的定時方法,他們的語法基本相同,但完成功能的卻是不同的。
- settimeout方法是定時程序兼丰,也就是在到達某個指定時間后灰粮,執(zhí)行什么事。(執(zhí)行一次就拉倒)
- setinterval方法則是表示間隔一定時間反復執(zhí)行某些事蜈漓。
定時器的返回值
- 當我們設置定時器時(不管是setTimeout還是setInterval),都會有一個返回值宫盔。這個返回值是一個數字融虽,代表當前是在瀏覽器中設置的第幾個定時器(返回的是定時器序號)。
let timer1 = setTimeout(() => { }, 1000) console.log(timer1) // 1 let timer2 = setInterval(() => { },1000) console.log(timer2) // 2
- 根據上面兩端代碼可以知道
- 1.setTimeout和setInterval雖然是處理不同功能的定時器灼芭,但都是瀏覽器的定時器有额,所以返回的序號是依次排列的。
- 2.setInterval設置完成定時器會有一個返回值彼绷,不管執(zhí)行多少次巍佑,這個代表序號的返回值不變(設置定時器就有返回值,執(zhí)行多少次是定時器的處理)寄悯。
- 根據上面兩端代碼可以知道
定時器的清除
clearTimeout([定時器的排隊序號])
-
clearInterval([定時器的排隊序號])
let timer = setTimeout(() => { // 定時器即使清除了萤衰,其返回值也不會清除,之后設置定時器的返回值也會在其返回值的基礎上繼續(xù)向后排猜旬, // 類似于銀行的排隊領號脆栋,即使1號的業(yè)務辦理完了倦卖,后面的人仍是從2號開始繼續(xù)領號,而不是從1開始椿争。 clearTimeout(timer) }, 1000)
注意: 定時器需要手動清除怕膛,并且clearTimeout和clearInterval都可以清除setTimeout或setInterval,但并不建議這樣做秦踪,容易造成混淆褐捻。
定時器的this指向
- 作為第一個參數的函數將會在全局作用域中執(zhí)行,因此函數內的this將會指向這個全局對象
let obj = { fn() { console.log(this) // obj // 示例1 let timer1 = setTimeout(function() { console.log('我是timer1的this指向:', this) // Window }, 1000) // 示例2 (讓定時器函數中的this是obj:使用變量保存的方式) let _this = this let timer2 = setTimeout(function() { console.log('我是timer2的this指向:', _this) // obj }, 1000) // 示例3 (讓定時器函數中的this是obj:使用bind方法改變this指針) let timer3 = setTimeout( function() { console.log('我是timer3的this指向:', this) // obj }.bind(this), 1000 ) // 示例4 (讓定時器函數中的this是obj:使用箭頭函數椅邓,箭頭函數中的this繼承宿主環(huán)境(上級作用域中的this)) let timer4 = setTimeout(() => { console.log('我是timer4的this指向:', this) // obj }, 1000) } } obj.fn()
setTimeout和setInterval是如何工作的柠逞?
涉及到的知識點:JS事件循環(huán)機制EVENTLOOP
- 首先,Javascript是一門單線程的非阻塞的腳本語言:用來與瀏覽器交互希坚。
- 單線程:同一時間只能執(zhí)行一個任務边苹,其他任務就得排隊,后續(xù)任務必須等到前一個任務結束才能開始執(zhí)行裁僧。
- 非阻塞:同步任務直接在主線程隊列中順序執(zhí)行个束,而異步任務會進入另一個任務隊列,不會阻塞主線程聊疲。等到主線程隊列空了(執(zhí)行完了)的時候茬底,就會去異步隊列查詢是否有可執(zhí)行的異步任務了(異步任務通常進入異步隊列之后還要等一些條件才能執(zhí)行,如ajax請求获洲、文件讀寫)阱表,如果某個異步任務可以執(zhí)行了便加入主線程隊列,以此循環(huán)贡珊。
注意:異步任務之間并不相同最爬,他們的執(zhí)行優(yōu)先級有區(qū)別。不同的異步任務會被分為兩類:微任務(micro task)和宏任務(macro task)
- 微任務:new promise()门岔,new MutaionObserver()
- 宏任務:setInterval()爱致,setTimeout()
主線程空閑的時候會先去查看微任務隊列是否有事件存在,如果存在就會對微任務隊列的事件依次調用寒随,直到為空糠悯。然后再對宏任務隊列依次執(zhí)行,進入循環(huán)妻往。
使用定時器的時候互艾,千萬不要太相信預期,延遲的時間嚴格來說總是大于xxx毫秒的讯泣,至于大多少就要看當時執(zhí)行的情況了纫普。即使設置為0也不會馬上執(zhí)行,HTM5規(guī)范定最小延遲時間不能小于4ms好渠,不同瀏覽器的實現不一樣局嘁,比如溉箕,Chrome可以設置1ms晦墙,IE11/Edge是4ms悦昵。
setTimeout
setTimeout注冊的函數fn會交給瀏覽器的定時器模塊來管理,延遲時間到了就將fn加入主進程執(zhí)行隊列晌畅,如果隊列前面還有沒有執(zhí)行完的代碼但指,則又需要花一點時間等待才能執(zhí)行到fn,所以實際的延遲時間會比設置的長抗楔。如在fn之前正好有一個超級大循環(huán)棋凳,那延遲時間就不是一丁點了。
(function testSetTimeout() {
console.time('timer');
const timer = setTimeout(() => {
console.timeEnd('timer');
}, 10);
for(let i = 0; i < 100000000; i++) {}
})();
// timer: 59.364990234375ms 遠遠不止10ms
setInterval
為什么盡量別用setInterval连躏?剩岳??
setInterval無視代碼錯誤
setInterval有個討厭的習慣入热,即對自己調用的代碼是否報錯這件事漠不關心拍棕,如果setInterval執(zhí)行的代碼由于某種原因出了錯,它還會持續(xù)不斷(不管不顧)地調用該代碼勺良。
function a() {
try {
cnosole.log('單詞拼寫錯誤,應該是console')
} catch (e) {
console.log('錯誤了')
}
}
setInterval(a, 1000)
// 8VM69:5 錯誤了 (控制臺每間隔一秒就會輸出一個錯誤了)
setInterval無視網絡延遲
假設你每隔一段時間就通過Ajax輪詢一次服務器绰播,看看有沒有新數據。而由于某些原因(服務器過載尚困、臨時斷網蠢箩、流量劇增、用戶帶寬受限事甜,等等谬泌,你的請求要花的時間遠比你想象的要長。但setInterval不在乎逻谦。它仍然會按定時持續(xù)不斷地觸發(fā)請求掌实,最終你的客戶端網絡隊列會塞滿Ajax調用。
例子:下面代碼并不是上一次fn執(zhí)行完了之后再過100ms才開始執(zhí)行下一次fn跨跨。 事實上潮峦,setInterval并不管上一次fn的執(zhí)行結果,而是每隔100ms就將fn放入異步隊列勇婴,而兩次fn之間具體間隔多久就不一定了忱嘹,跟setTimeout實際延遲時間類似,和JS執(zhí)行情況有關耕渴。
(function testSetInterval() {
let i = 0;
const start = Date.now();
const timer = setInterval(() => {
i++;
i === 2 && clearInterval(timer);
console.log(`第${i}次開始`, Date.now() - start);
for(let i = 0; i < 900000000; i++) {}
console.log(`第${i}次結束`, Date.now() - start);
}, 100);
})();
VM232:7 第1次開始 104
VM232:9 第1次結束 603
VM232:7 第2次開始 605
VM232:9 第2次結束 1106
雖然每次fn執(zhí)行時間都很長拘悦,但下一次并不是等上一次執(zhí)行完了再過100ms才開始執(zhí)行的,實際上早就已經等在隊列里了橱脸。
在fn被阻塞的時候础米,setInterval仍然在組織將來對回調函數的調用分苇。 因此,當第一次fn函數調用結束時屁桑,已經有6次函數調用在等待執(zhí)行医寿。
處理可能的阻塞調用
最簡單也是最容易控制的方案,是在回調函數內部使用setTimeout函數蘑斧。
function foo(){
setTimeout(foo, 100);
}
foo();