JavaScript-函數節流

在上一篇文章 JavaScript-函數防抖 中我們學習了什麼是防抖,並且一步步實現了防抖函數,今天我們一起來學習節流(throttle)。

什麼是節流

函數節流(throttle):當持續觸發事件時,保證一定時間段內只調用一次事件處理函數。簡單的說,就是讓一個函數無法在很短時間間隔內被連續調用,只有當上一次函數執行後過了規定的時間間隔,才能進行下一次函數的執行。

函數節流主要有兩種實現方法:時間戳和定時器。

歡迎關注我的微信公衆號:前端極客技術(FrontGeek)

節流的實現

時間戳

思路

只要觸發,就用Date方法獲取當前時間 now,與上一次調用的時間 previous 作比較

  • 如果時間差大於等於規定的時間間隔,就執行一次目標函數,執行以後,將存儲上一次調用時間previous的值更新爲當前時間now

  • 如果時間差小於規定的時間間隔,則等待下一次觸發重新進行第一步操作。

代碼實現

// delay:規定的時間間隔
function throttle(func, delay) {
  var context, args
  var previous = 0
  return function() {
    context = this
    args = arguments
    var now = +new Date()
    if (now - previous >= delay) {
      func.apply(context, args)
      previous = now
    }
  }
}

我們依舊採用防抖那篇文章用到的鼠標移動的例子來驗證節流,調用節流函數方式如下:

container.onmousemove = throttle(mouseMove, 2000)

效果如下:
在這裏插入圖片描述

從上圖中可以看到:當鼠標移入時,事件立即執行,每過2秒會執行一次,假設在第7秒時移出,停止觸發,以後不會再執行事件。

定時器

思路

用定時器實現時間間隔。

  • 當定時器不存在,說明可以執行函數,定義一個定時器來向任務隊列註冊目標函數。目標函數執行後設置保存定時器ID變量爲空
  • 當定時器已經被定義,說明已經在等待過程中,則等待下次觸發事件時再進行查看。

代碼實現

function throttle(func, delay) {
  var timeout = null
  var context, args
  return function() {
    context = this
    args = arguments
    if (!timeout) {
      timeout = setTimeout(function() {
        timeout = null
        func.apply(context, args)
      }, delay)
    }
  }
}

代碼執行效果如下:
在這裏插入圖片描述

從上面的動圖我們可以看到:鼠標移入時,事件不會立即執行,之後每隔2秒執行一次,假設在第5秒時移出,事件停止觸發,但在第6秒時依舊會執行一次事件。

兩者區別:

  • 時間戳實現:觸發事件一發生先執行目標函數,然後再等待規定的時間間隔再次執行目標函數。如果在等待過程中停止觸發,後續不會再執行目標函數。
  • 定時器實現:觸發事件一發生,先等待夠規定的時間間隔再執行目標函數。即使在等待過程中停止觸發,若定時器已經在任務隊列裏註冊了定時器,也會執行最後一次。

強強聯合:時間戳+定時器

如果我們想要能夠控制鼠標移入能夠立即執行,停止觸發的時候能夠再執行一次,我們可以綜合時間戳和定時器兩種方法來實現“有頭有尾”的效果。

在這裏我們需要注意:控制好在上一週期的“尾”和下一週期的“頭”之間時間間隔,我們引入變量remaining表示還需要等待的時間,來讓尾部那一次的執行也符合時間間隔。

代碼實現

function throttle(func, delay) {
	var timeout, context, args, result
	var previous = 0
	
	var throttled = function() {
		context = this
		args = arguments
		var now = +new Date()
		// 下次觸發func剩餘時間
		var remaining = delay - (now - previous)
    // 如果沒有剩餘的時間了或者你改了系統時間
		if (remaining <= 0 || remaining > delay) {
			if (timeout) {
				clearTimeout(timeout)
				timeout = null
			}
			func.apply(context, args)
			previous = now
		} else if (!timeout) {
			timeout = setTimeout(function(){
				previous = +new Date()
				timeout = null
				func.apply(context, args)
			}, remaining)
		}
	}
  return throttled
}

代碼執行效果如下:
在這裏插入圖片描述

優化

在上面結合時間戳和定時器的解法的基礎上,如果我們想實現是否啓用第一次 / 尾部最後一次計時回調的執行,如何實現?

我們可以設置個options作爲第三個參數,然後根據傳的值判斷到底哪種效果,我們約定:

  • leading:false表示禁用第一次執行
  • trailing:false表示禁用停止觸發的回調

代碼實現如下:

function throttle(func, delay, options) {
  var timeout, context, args, result
  var previous = 0
  if (!options) options = {}

  var later = function() {
    previous = options.leading === false ? 0 : new Date().getTime()
    timeout = null
    func.apply(context, args)
    if (!timeout) context = args = null
  }

  var throttled = function() {
    var now = new Date().getTime()
    if (!previous && options.leading === false) previous = now
    var remaining = delay - (now - previous)
    context = this
    args = arguments
    if (remaining <= 0 || remaining > delay) {
      if (timeout) {
        clearTimeout(timeout)
        timeout = null
      }
      previous = now
      func.apply(context, args)
      if (!timeout) context = args = null
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining)
    }
  }
  return throttled
}

我們想要第一次立即執行,並且禁用停止觸發的回調,調用throttle方法如下:

container.onmousemove = throttle(mouseMove, 2000, {leading: true, trailing: false})

效果如下圖:
在這裏插入圖片描述

取消

在防抖函數中,我們實現了cancel方法,在節流函數中,同理:

function throttle(func, delay, options) {
  .....

  var throttled = function() {
    ....
  }

  throttled.cancel = function() {
    clearTimeout(timeout)
    previous = 0
    timeout = null
  }

  return throttled
}

存在的問題

至此,一個完整的節流函數已經實現好了,但是仍然存在一個問題:就是 leading:false 和 trailing: false 不能同時設置。

如果同時設置的話,比如當你將鼠標移出的時候,因爲 trailing 設置爲 false,停止觸發的時候不會設置定時器,所以只要再過了設置的時間,再移入的話,就會立刻執行,就違反了 leading: false,bug 就出來了。如下圖所示:
在這裏插入圖片描述

總結

防抖和節流的作用都是防止函數多次調用。區別在於:假設一個用戶一直觸發這個函數,且每次觸發函數的間隔小於wait,防抖的情況下只會調用一次,而節流的情況會每隔一段時間wait調用函數。

相比 debounce,throttle 要更加寬鬆一些,其目的在於:按頻率執行調用。

參考來源:https://github.com/mqyqingfeng/Blog/issues/26

歡迎關注我的微信公衆號:前端極客技術(FrontGeek)
在這裏插入圖片描述

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