說說 JavaScript 中函數的防抖 (Debounce) 與節流 (Throttle)

爲什麼要防抖和節流

有時候會在項目開發中頻繁地觸發一些事件,如 resizescrollkeyupkeydown等,或者諸如輸入框的實時搜索功能,咱們知道若是事件處理函數無限制調用,會大大加劇瀏覽器的工做量,有可能致使頁面卡頓影響體驗;後臺接口的頻繁調用,不只會影響客戶端體驗,還會大大增長服務器的負擔。而若是對這些調用函數增長一個限制,讓其減小調用頻率,豈不美哉?瀏覽器

針對這個問題,通常有兩個方案: 防抖 (Debounce) 節流 (Throttle)服務器

防抖(Debounce)

我對函數防抖的定義:當函數被連續調用時,該函數並不執行,只有當其所有中止調用超過必定時間後才執行1次。app

一個被常常提起的例子:dom

上電梯的時候,你們陸陸續續進來,電梯的門不會關上,只有當一段時間都沒有人上來,電梯纔會關門。函數

Talk is cheap,咱們直接 show code 吧。工具

先作基本的準備(篇幅緣由,HTML部分省略):ui

let container = document.getElementById('container');

// 事件處理函數
function handle(e) {
    console.log(Math.random()); 
}

// 添加滾動事件
container.addEventListener('scroll', handle);

複製代碼

咱們發現,每滾動一下,控制檯就會打印出一行隨機數。this

基礎防抖

咱們如今寫一個最基礎的防抖處理:spa

function debounce(func, wait) {
    var timeout;//標記
    return function() {
      clearTimeout(timeout);
      timeout = setTimeout(func, wait);
    }
}
複製代碼

事件也作以下改寫:code

container.addEventListener('scroll', debounce(handle, 1000));
複製代碼

如今試一下, 咱們會發現只有咱們中止滾動1秒鐘的時候,控制檯纔會打印出一行隨機數。

標準防抖

以上基礎版本會有兩個問題,請看以下代碼:

// 處理函數
function handle(e) {
    console.log(this); //輸出Window對象
    console.log(e); //undefined
}
複製代碼

沒錯,當咱們不使用防抖處理時,handle()函數的this指向調用此函數的container,而在外層使用防抖處理後,this的指向會變成Window。 其次,咱們也要獲取到事件對象event

因此咱們要對防抖函數作如下改寫:

function debounce(fn, wait) {
  let timeout;
  return function() {
    let that = this;
    let arg = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(function(){
      fn.apply(that,arg)//使用apply改變this指向
    }, wait);
  }
}

複製代碼

固然了,若是使用箭頭函數即可以省去外層聲明。

先觸發式防抖

以上的狀況都是隻有當連續觸發中止後才執行,那若是咱們想讓事件第一次觸發就執行,後面的連續觸發都不執行,直到中止觸發一段時間才能夠再次觸發(好比防止頻繁點擊),該如何處理呢?

那麼能夠利用一樣的原理,稍做修改便可:

function debounce(fn, wait) {
    let timeout;
    return function(){
      let arg = arguments;
      let that = this;
      clearTimeout(timeout);
      !timeout && fn.apply(that,arg)
        timeout = setTimeout(function(){
          timeout = null;
        }, wait);
    }
}
複製代碼

節流 (Throttle)

顧名思義,節流就是節約流量,將連續觸發的事件稀釋成預設評率。 好比每間隔1秒執行一次函數,不管這期間觸發多少次事件。

這有點像公交車, 不管在站點等車的人多很少,公交車只會按時來一班,不會來一我的就來一輛公交車。

標準節流

function throttle(fn, wait) {
  let timeout; 
  return function () {
    if (!timeout) { 
      timeout = setTimeout(() => {
        timeout = null;
        fn.apply(this, arguments)
      }, wait)
    }
  }
}

複製代碼

用滾動事件來描述節流,實際上是一個很是典型的場景,好比須要用滾動事件判斷是否加載更多等。

先觸發式節流

和防抖函數相似,以上的狀況是先等待後觸發,若是咱們想讓事件先觸發後等待,該如何處理呢?網上大部分文章都告訴你用時間戳的方式去實現,其實只要像防抖同樣稍做修改便可實現。

function throttle(fn, wait) {
  let timeout; 
  return function () {
    if (!timeout) { 
      fn.apply(this, arguments)
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
    }
  }
}
複製代碼

這樣,咱們就會發現第一次觸發函數就會當即生效。

總結

關於防抖與節流,lodash、underscore等工具庫都有完善的實現能夠直接用,本沒有必要造輪子。本文的目的僅僅是爲了將其主要思想和實現思路展示出來。更重要的,知道防抖和節流的本質後,就知道在什麼時候使用防抖或者節流,什麼時候先觸發或後觸發。不管需求如何改變,均可以靈活的運用。

相關文章
相關標籤/搜索