定時器


定時器


JavaScript提供定時執行代碼的功能,叫作定時器(timer),主要由setTimeout()和setInterval()這兩個函數來完成。javascript


setTimeout()


setTimeout函數用來指定某個函數或某段代碼,在多少毫秒以後執行。它返回一個整數,表示定時器的編號,之後能夠用來取消這個定時器。html

var timerId = setTimeout(func|code, delay)java

上面代碼中,setTimeout函數接受兩個參數,第一個參數func|code是將要推遲執行的函數名或者一段代碼,第二個參數delay是推遲執行的毫秒數。安全

console.log(1);
setTimeout('console.log(2)',1000);
console.log(3);
複製代碼

上面代碼的輸出結果就是1,3,2,由於setTimeout指定第二行語句推遲1000毫秒再執行。bash

須要注意的是,推遲執行的代碼必須以字符串的形式,放入setTimeout,由於引擎內部使用eval函數,將字符串轉爲代碼。若是推遲執行的是函數,則能夠直接將函數名,放入setTimeout。一方面eval函數有安全顧慮,另外一方面爲了便於JavaScript引擎優化代碼,setTimeout方法通常老是採用函數名的形式,就像下面這樣。異步

function f(){
  console.log(2);
}

setTimeout(f,1000);

// 或者

setTimeout(function (){console.log(2)},1000);
複製代碼

setTimeout定時器是有編號的,能夠對這個編號執行操做去操做這個定時器函數


setInterval()


setInterval函數的用法與setTimeout徹底一致,區別僅僅在於setInterval指定某個任務每隔一段時間就執行一次,也就是無限次的定時執行。oop

var i = 1
  var timer = setInterval(function() {
    console.log(i++);
  }, 1000);
複製代碼

上面代碼表示每隔1000毫秒就輸出一個2,直到用戶點擊了中止按鈕。測試


clearTimeout(),clearInterval()


setTimeout和setInterval函數,都返回一個表示計數器編號的整數值,將該整數傳入clearTimeout和clearInterval函數,就能夠取消對應的定時器。優化

var id1 = setTimeout(f,1000);
var id2 = setInterval(f,1000);

clearTimeout(id1);
clearInterval(id2);

複製代碼

異步操做


參考

通常的同步代碼是放在棧內存中一個個從上往下執行,可是和事件相關的異步操做不是保存在棧內存中而是放在任務隊列中,任務隊列中的代碼必須等到棧內存中代碼所有執行完畢後纔開始執行任務隊列中的代碼。

定時器中的時間設置爲0是否就是當即執行?時間設置成100毫秒是否是就必定就是嚴格意義上的100毫秒後執行?實際上這個事件只是儘可能的作到靠近這個時間後執行代碼,若是異步代碼以前還有不少同步的代碼須要執行,必須等他們所有執行完畢後才能執行,只是時間會盡可能靠近100毫秒。JS引擎通常會對異步操做設置最小的時間間隔,若是設置的是0毫秒後執行代碼,可是JS引擎最小的時間間隔是4毫秒,那麼最終就是4毫秒後,因此定時器中設置的時間間隔不是嚴格意義上的時間,而是隻是會盡可能的靠近這個時間。


運行機制


setTimeout和setInterval的運行機制是,將指定的代碼移出本次執行,等到下一輪Event Loop時,再檢查是否到了指定時間。若是到了,就執行對應的代碼;若是不到,就等到再下一輪Event Loop時從新判斷。這意味着,setTimeout指定的代碼,必須等到本次執行的全部代碼都執行完,纔會執行。

setTimeout的做用是將代碼推遲到指定時間執行,若是指定時間爲0,即setTimeout(f,0),那麼不會馬上執行

setTimeout(f,0)將第二個參數設爲0,做用是讓f在現有的任務(腳本的同步任務和「任務隊列」中已有的事件)一結束就馬上執行。也就是說,setTimeout(f,0)的做用是,儘量早地執行指定的任務。

測試題1:如下代碼輸出什麼

var i=0;
for(var i=0; i<10; i++){
  setTimeout(function(){
      console.log(i)
  }, 1000)
}            
複製代碼

會執行10次的輸出10,覺得異步操做必須等到同步操做完成以後才能去工做,for循環完成後全局的i就變成了10,因此輸出10次的10

測試題2:如下代碼輸出什麼

var t = true;  
setTimeout(function(){ 
  t = false; 
}, 1000);  

while(t){ }  
console.log('end')
複製代碼

會一直執行輸出end由於while是一個同步操做,也是一個死循環,異步操做永遠得不到執行


異步與回調


function f1(callback){
    setTimeout(function(){
        //作某件事,可能好久
        console.log('別急,開始執行f1')
        for(var i=0;i< 100000;i++){

        }
        console.log('f1執行完了')

        callback()
    }, 0);

}
function f2(){
    console.log('執行f2');
}
function f3(){
    console.log('執行f3');
}
f1(f2) //當f1執行完以後再執行 f2
f3()
複製代碼

函數節流


var timer
  function hiFrequency(){
      if(timer){
          clearTimeout(timer)
      }
        timer = setTimeout(function(){
             console.log('do something')
        }, 3000)
  }

  hiFrequency()
  hiFrequency()
  hiFrequency()
複製代碼

建立一個定時器,3秒後執行輸出"do something"若是在還未到3秒就再次執行調用函數,那麼此時因爲已經建立了定時器timer,此時timer爲true,因此就執行清除了定時器沒有任何輸出。若是再執行函數(執行函數的瞬間幾乎同時就已經建立了定時器)停下3秒就會輸出"do something"

改造

function throttle(fn, delay) {
    var timer = null
    return function(){
        clearTimeout(timer)
        timer = setTimeout(function(){ 
            fn(arguments)
        }, delay)
    }
}

function fn(){
    console.log('hello ')
}

var fn2 = throttle(fn, 1000)
fn2()
fn2()
fn2()
複製代碼

函數節流就是一個功能執行的特別頻繁,好比監聽用戶的鼠標滾動的事件,用戶可能會滾動不少次,咱們不須要每次鼠標滾動都執行一個操做,須要用戶鼠標滾動停下來後去執行一個操做,這個時候就須要函數節流。若是一個東西執行不少次,咱們以最後一次爲準,就是函數節流。