定時器 setTimeout & setInterval

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:71次開始 104
VM232:91次結束 603
VM232:72次開始 605
VM232:92次結束 1106

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

處理可能的阻塞調用

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

function foo(){
    setTimeout(foo, 100);
}
foo();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章