JavaScript淺析 -- 定時器和節流防抖

weixin_33758863發表於2018-07-20

一、定時器

js的定時器主要有setTimeout和setInterval兩個,下面我們分別講講。

1. setTimeout

setTimeout()設定任務延遲多久之後才執行,該函式第一個引數可以是可執行語句(字串形式)或者一個函式,第二個引數是延遲的毫秒數(若省略則預設為0),後續的引數則代表第一個函式的引數。

// 下面兩者一樣
setTimeout('console.log(666)',1000);
setTimeout(function() {
  console.log(666);
}, 1000);

setTimeout('console.log(666)'); // 相當於setTimeout('console.log(666)', 0);

setTimeout(function(a, b) {
  console.log(a + b); // 3
}, 1000, 1, 2);

其中有兩點要注意(具體原因可以看這篇文章):

  • 函式裡面的this指向的是全域性,因為賦值的是函式的地址,執行的時候是從任務佇列取出來在主執行棧中全域性呼叫。
var obj = {
  y: function () {
    console.log(this);
  }
};
setTimeout(obj.y, 1000) // window
  • 定時器設定的毫秒數是理想情況下多少毫秒後執行,但實際情況下是可能多於該毫秒數之後才執行。因為雖然數完秒就放到任務佇列,但要等主執行棧為空了才會去呼叫這些任務佇列的回撥函式。
// 下面的雖然寫了1s後執行輸出666,但實際卻要3s後才執行輸出666
setTimeout('console.log(666)', 1000);
function execTime(t) {
  var start = Date.now();
  while(Date.now() - start < t) {}
}
execTime(3000);
2. setInterval

setInterval與setTimeout用法和注意點基本差不多,他主要用於設定時間間隔,每隔多少毫秒之後重複執行任務,直到瀏覽器關閉或手動清除。

實際上,兩次函式執行的間隔時間也不是所設定的毫秒數,而是小於執行的毫秒數的,比如設定了100ms,第一次函式執行用了10ms,那麼間隔90ms之後第二次執行函式;如果第一次函式執行用了110ms,那麼函式執行完畢之後會立馬執行第二次函式。那如果要固定間隔時間執行某函式,可以用setTimeout來模擬。

var timer = setTimeout(function f() {
  // ...
  timer = setTimeout(f, 2000);
}, 2000);
3. 清除定時器

setTimeout和setInterval生成的都是計數器編號,從0開始,將對應的編號傳入clearTimeout和clearInterval就可以清除對應的定時器。

var a = 0;
var timer = setInterval(function() {
  console.log(a++); // 輸出0、1就停了
  if (a===2) {
    clearInterval(timer);
  }
}, 1000);

當瀏覽器的標籤頁開啟之後,每生成一個定時器都是從上一個編號開始累加上去的,第一個定時器編號是0,第二個是1,如此類推,所以清除所有的定時器可以這麼實現。

var timer = setTimeout(function(){}, 0);
while(timer >= 0) {
  clearTimeout(timer);
  timer--;
}

二、節流和防抖

通過定時器,我們可以實現函式節流和函式防抖。

1. 函式節流

函式節流指的是在規定時間內重複觸發不重複執行,以節流。如監聽頁面滾動到底的時候載入新的資料,但往往會連續觸發滾動事件導致連續發起幾次請求,此時就需要函式節流。

function throttle(fn, delay) {
  var isFinished = true;
  return function() {
      if (!isFinished) return;
      isFinished = false;
      var that = this, args = arguments;
      setTimeout(function() {
          fn.apply(that, args);
          isFinished = true;
      }, delay);
  };
}
document.onscroll = throttle(function() { /* 請求資料 */ }, 20);

正如上面的程式碼,函式節流主要是鎖的思想。先執行一次,當上一個執行完畢才解鎖isFinished = true,否則鎖住isFinished = false程式碼不讓執行。

2. 函式防抖

函式防抖指的是規定時間內重複觸發只執行最後一次,以防止頻繁觸發帶來的抖動。如輸入框搜尋時,如果每當輸入框有一個字變化就搜尋,一是對頻繁請求對伺服器壓力大,二是前端也會頻繁的閃現搜尋的結果,使用者體驗不好。所以一般都會有個延時,等使用者輸入延遲一段時間後確定不再輸入再進行搜尋。

function debounce(fn, delay) {
    var timer = null;
    return function() {
        clearTimeout(timer);
        var that = this, args = arguments;
        setTimeout(function() {
            fn.apply(that, args);
        }, delay);
    };
}
document.querySelector('input').onchange = debounce(function() { /* 請求資料 */ }, 200);

函式防抖的主要思想是定時器清除。規定時間內再次觸發,則上一個未被執行的定時器被清除,再開啟新的定時器,直到最後一次觸發。

上面寫的是兩個簡單版本的函式節流和函式防抖,主要是為了講解兩者的主要思想,也好進行區分:

  • 節流是先執行一次,然後過規定時間之後才能再執行。防抖是過了規定時間之後沒再觸發,最後執行一次
  • 節流是鎖的思想,防抖是清除定時器的思想。
  • 注意最後返回結果都是函式,而且要注意函式中的this指向是返回函式的this。

相關文章