setTimeout和setInteval是window對象上兩個主要的定時方法,他們的語法基本相同,但完成功能的卻是不同的。
- settimeout方法是定時程序,也就是在到達某個指定時間後,執行什麼事。(執行一次就拉倒)
- setinterval方法則是表示間隔一定時間反覆執行某些事。
定時器的返回值
- 當我們設置定時器時(不管是setTimeout還是setInterval),都會有一個返回值。這個返回值是一個數字,代表當前是在瀏覽器中設置的第幾個定時器(返回的是定時器序號)。
let timer1 = setTimeout(() => { }, 1000) console.log(timer1) // 1 let timer2 = setInterval(() => { },1000) console.log(timer2) // 2
- 根據上面兩端代碼可以知道
- 1.setTimeout和setInterval雖然是處理不同功能的定時器,但都是瀏覽器的定時器,所以返回的序號是依次排列的。
- 2.setInterval設置完成定時器會有一個返回值,不管執行多少次,這個代表序號的返回值不變(設置定時器就有返回值,執行多少次是定時器的處理)。
- 根據上面兩端代碼可以知道
定時器的清除
-
clearTimeout([定時器的排隊序號])
-
clearInterval([定時器的排隊序號])
let timer = setTimeout(() => { // 定時器即使清除了,其返回值也不會清除,之後設置定時器的返回值也會在其返回值的基礎上繼續向後排, // 類似於銀行的排隊領號,即使1號的業務辦理完了,後面的人仍是從2號開始繼續領號,而不是從1開始。 clearTimeout(timer) }, 1000)
注意: 定時器需要手動清除,並且clearTimeout和clearInterval都可以清除setTimeout或setInterval,但並不建議這樣做,容易造成混淆。
定時器的this指向
- 作爲第一個參數的函數將會在全局作用域中執行,因此函數內的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繼承宿主環境(上級作用域中的this)) let timer4 = setTimeout(() => { console.log('我是timer4的this指向:', this) // obj }, 1000) } } obj.fn()
setTimeout和setInterval是如何工作的?
涉及到的知識點:JS事件循環機制EVENTLOOP
- 首先,Javascript是一門單線程的非阻塞的腳本語言:用來與瀏覽器交互。
- 單線程:同一時間只能執行一個任務,其他任務就得排隊,後續任務必須等到前一個任務結束才能開始執行。
- 非阻塞:同步任務直接在主線程隊列中順序執行,而異步任務會進入另一個任務隊列,不會阻塞主線程。等到主線程隊列空了(執行完了)的時候,就會去異步隊列查詢是否有可執行的異步任務了(異步任務通常進入異步隊列之後還要等一些條件才能執行,如ajax請求、文件讀寫),如果某個異步任務可以執行了便加入主線程隊列,以此循環。
注意:異步任務之間並不相同,他們的執行優先級有區別。不同的異步任務會被分爲兩類:微任務(micro task)和宏任務(macro task)
- 微任務:new promise(),new MutaionObserver()
- 宏任務:setInterval(),setTimeout()
主線程空閒的時候會先去查看微任務隊列是否有事件存在,如果存在就會對微任務隊列的事件依次調用,直到爲空。然後再對宏任務隊列依次執行,進入循環。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DaCAPktU-1579248345336)(./images/26782.png)]
使用定時器的時候,千萬不要太相信預期,延遲的時間嚴格來說總是大於xxx毫秒的,至於大多少就要看當時執行的情況了。即使設置爲0也不會馬上執行,HTM5規範定最小延遲時間不能小於4ms,不同瀏覽器的實現不一樣,比如,Chrome可以設置1ms,IE11/Edge是4ms。
setTimeout
setTimeout註冊的函數fn會交給瀏覽器的定時器模塊來管理,延遲時間到了就將fn加入主進程執行隊列,如果隊列前面還有沒有執行完的代碼,則又需要花一點時間等待才能執行到fn,所以實際的延遲時間會比設置的長。如在fn之前正好有一個超級大循環,那延遲時間就不是一丁點了。
(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執行的代碼由於某種原因出了錯,它還會持續不斷(不管不顧)地調用該代碼。
function a() {
try {
cnosole.log('單詞拼寫錯誤,應該是console')
} catch (e) {
console.log('錯誤了')
}
}
setInterval(a, 1000)
// 8VM69:5 錯誤了 (控制檯每間隔一秒就會輸出一個錯誤了)
setInterval無視網絡延遲
假設你每隔一段時間就通過Ajax輪詢一次服務器,看看有沒有新數據。而由於某些原因(服務器過載、臨時斷網、流量劇增、用戶帶寬受限,等等,你的請求要花的時間遠比你想象的要長。但setInterval不在乎。它仍然會按定時持續不斷地觸發請求,最終你的客戶端網絡隊列會塞滿Ajax調用。
例子:下面代碼並不是上一次fn執行完了之後再過100ms纔開始執行下一次fn。 事實上,setInterval並不管上一次fn的執行結果,而是每隔100ms就將fn放入異步隊列,而兩次fn之間具體間隔多久就不一定了,跟setTimeout實際延遲時間類似,和JS執行情況有關。
(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執行時間都很長,但下一次並不是等上一次執行完了再過100ms纔開始執行的,實際上早就已經等在隊列裏了。
在fn被阻塞的時候,setInterval仍然在組織將來對回調函數的調用。 因此,當第一次fn函數調用結束時,已經有6次函數調用在等待執行。
處理可能的阻塞調用
最簡單也是最容易控制的方案,是在回調函數內部使用setTimeout函數。
function foo(){
setTimeout(foo, 100);
}
foo();