關於setTimeout & setInterval

前言

之因此寫這篇文章是由於上週工做中使用setInterval輪詢請求接口時遇到了一些問題,若是哪裏理解的不對請你們多多指教~javascript

進入正題

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([定時器的排隊序號])java

  • clearInterval([定時器的排隊序號])ajax

    let timer = setTimeout(() => {
        // 定時器即便清除了,其返回值也不會清除,以後設置定時器的返回值也會在其返回值的基礎上繼續向後排,
        // 相似於銀行的排隊領號,即便1號的業務辦理完了,後面的人還是從2號開始繼續領號,而不是從1開始。
        clearTimeout(timer)
      }, 1000)
    複製代碼

注意: 定時器須要手動清除,而且clearTimeout和clearInterval均可以清除setTimeout或setInterval,但並不建議這樣作,容易形成混淆。promise

定時器的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() 主線程空閒的時候會先去查看微任務隊列是否有事件存在,若是存在就會對微任務隊列的事件依次調用,直到爲空。而後再對宏任務隊列依次執行,進入循環。

^ ^.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調用。ui

例子:下面代碼並非上一次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:71次開始 104
VM232:91次結束 603
VM232:72次開始 605
VM232:92次結束 1106
複製代碼

雖然每次fn執行時間都很長,但下一次並非等上一次執行完了再過100ms纔開始執行的,實際上早就已經等在隊列裏了。 在fn被阻塞的時候,setInterval仍然在組織未來對回調函數的調用。 所以,當第一次fn函數調用結束時,已經有6次函數調用在等待執行。

處理可能的阻塞調用

最簡單也是最容易控制的方案,是在回調函數內部使用setTimeout函數。

function foo(){
    setTimeout(foo, 100);
}
foo();
複製代碼
參考連接
相關文章
相關標籤/搜索