JS中防抖在思考(避免連續點擊查詢)

前幾天在項目中看見防止查詢屢次點擊的一種寫法緩存

/** * 相似於一種簡單的防抖思想的實現 */
    search = () => {
        clearTimeout(this.timer);
        this.timer = setTimeout(() => {
            fetch('url').then((res) => {
                //todo
            }).catch(() => {
                //todo
            })
        } ,1000);
    }
複製代碼

可是這樣會存在一種問題:app

第一種狀況(默認setTimeOut的時間爲1秒時)

1.當用戶點擊一次時,其實所用的時間爲接口請求時間加上1秒,當接口響應時間只有幾十毫秒時,這種作法就有點雞肋。函數

2.當用戶連續點擊10次時,其實前9次請求都被清除掉了(由於咱們連續點擊之間的時間間隔確定會小於1秒),只有最後一次點擊被執行,可是查詢的時間變爲了1秒加上接口請求時間fetch

第二種狀況(儘可能的縮短setTimeOut的時間,好比50ms)

1.當用戶點擊一次,其實所用的時間爲接口請求時間加上50毫秒ui

2.當用戶點擊10次時,假如說接口請求時間爲30毫秒,而用戶點擊的時間間隔爲200毫秒,也就是說當setTimeOut的時間加上接口請求時間小於用戶點擊的時間間隔時,並無限制住,其實至關於執行10次。this

我想聽一下你對這種寫法的理解,也不知道我對這種寫法理解對不對 ?

「其實在項目開發中較經常使用的一種寫法時,由於咱們大部分的查詢都添加了loading效果,並且loading效果的顯示是經過this.state.isLoading來進行判斷的,那麼這樣就好判斷了,點擊查詢時先判斷this.state.isLoading是否爲false,當爲false時,下發請求,爲true時,給出正在努力查詢中的提示。」搜索引擎

再回顧一下防抖概念:url

防抖(debounce)

你是否在平常開發中遇到一個問題,在滾動事件中須要作個複雜計算或者實現一個按鈕的防二次點擊操做。這些需求均可以經過函數防抖動來實現。尤爲是第一個需求,若是在頻繁的事件回調中作複雜計算,頗有可能致使頁面卡頓,不如將屢次計算合併爲一次計算,只在一個精確點作操做。spa

PS:防抖和節流的做用都是防止函數屢次調用。區別在於,假設一個用戶一直觸發這個函數,且每次觸發函數的間隔小於wait,防抖的狀況下只會調用一次,而節流的 狀況會每隔必定時間(參數wait)調用函數。code

咱們先來看一個袖珍版的防抖理解一下防抖的實現(相似項目中防止查詢屢次點擊):

// func是用戶傳入須要防抖的函數
// wait是等待時間
const debounce = (func, wait = 50) => {
   // 緩存一個定時器id
   let timer = 0
   // 這裏返回的函數是每次用戶實際調用的防抖函數
   // 若是已經設定過定時器了就清空上一次的定時器
   // 開始一個新的定時器,延遲執行用戶傳入的方法
   return function(...args) {
     if (timer) clearTimeout(timer)
     timer = setTimeout(() => {
       func.apply(this, args)
     }, wait)
   }
}
// 不難看出若是用戶調用該函數的間隔小於wait的狀況下,上一次的時間還未到就被清除了,並不會執行函數

複製代碼

其實個人理解就是防抖這種用法用戶防止用戶屢次點擊查詢好像不太特別的合適。其實它更多的場景是用於:搜索引擎搜索問題、用戶放縮窗口大小等場景。

  • 上面是一個簡單版的防抖,可是有缺陷,這個防抖只能在最後調用。通常的防抖會有immediate選項,表示是否當即調用: 例如在搜索引擎搜索問題的時候,咱們固然是但願用戶輸入完最後一個字才調用查詢接口,這個時候適用延遲執行的防抖函數,它老是在一連串(間隔小於wait的)函數觸發以後調用。
  • 例如用戶給interviewMap點star的時候,咱們但願用戶點第一下的時候就去調用接口,而且成功以後改變star按鈕的樣子,用戶就能夠立馬獲得反饋是否star成功了,這個狀況適用當即執行的防抖函數,它老是在第一次調用,而且下一次調用必須與前一次調用的時間間隔大於wait纔會觸發。
也就是把防抖函數改爲能夠支持防止點擊屢次查詢的狀況:

實現一個帶有當即執行選項的防抖函數

// 這個是用來獲取當前時間戳的
function now() {
  return +new Date()
}
/** * 防抖函數,返回函數連續調用時,空閒時間必須大於或等於 wait,func 纔會執行 * @param {function} func 回調函數 * @param {number} wait 表示時間窗口的間隔 * @param {boolean} immediate 設置爲ture時,是否當即調用函數 * @return {function} 返回客戶調用函數 */
function debounce (func, wait = 50, immediate = true) {
  let timer, context, args

  // 延遲執行函數
  const later = () => setTimeout(() => {
    // 延遲函數執行完畢,清空緩存的定時器序號
    timer = null
    // 延遲執行的狀況下,函數會在延遲函數中執行
    // 使用到以前緩存的參數和上下文
    if (!immediate) {
      func.apply(context, args)
      context = args = null
    }
  }, wait)

  // 這裏返回的函數是每次實際調用的函數
  return function(...params) {
    // 若是沒有建立延遲執行函數(later),就建立一個
    if (!timer) {
      timer = later()
      // 若是是當即執行,調用函數
      // 不然緩存參數和調用上下文
      if (immediate) {
        func.apply(this, params)
      } else {
        context = this
        args = params
      }
    // 若是已有延遲執行函數(later),調用的時候清除原來的並從新設定一個
    // 這樣作延遲函數會從新計時
    } else {
      clearTimeout(timer)
      timer = later()
    }
  }
}
複製代碼

上面函數的實現思路:

  1. 對於按鈕防點擊來講的實現:若是函數是當即執行的,就當即調用,若是函數是延遲執行的,就緩存上下文和參數,放到延遲函數中去執行。一旦我開始一個定時器,只要我定時器還在,你每次點擊我都從新計時。一旦你點累了,定時器時間到,定時器重置爲null,就能夠再次點擊了。
  2. 對於延時執行函數來講的實現:清除定時器ID,若是是延遲調用就調用函數

其實在項目開發中我的更喜歡經過loading這種效果,來作到防止用戶連續點擊查詢。

相關文章
相關標籤/搜索