簡單入門到徹底搞懂防抖和節流

       今日頭條出的題目是寫一個防抖函數,,根據用戶輸入的詞語,提交後端進行模糊查詢,按輸入次序顯示服務器返回結果。
    
       原題目,面試官寫的是注意防抖和次序。

      他的要求是: 比如輸入非常快的提交了三次 a, b,c 三個字母。由於服務器響應慢,客戶端已經提交c查詢,但是服務器才返回的b的查詢結果(這個用閉包,其實來閉包也有問題,典型如何處理異常)。
      

        面試官出的這道題的確很有代表性,前端開發中經常會遇到的問題,重複執行,表單重複提交,不管前後端開發都應該瞭解。這個題跟我大約2012年6月,騰訊網www.qq.com 首頁改版上線,做的一個搜索模塊很相似, 那時候在騰訊是我職業生涯中第一個前端開發工作。

        當時這個項目,填了幾個坑:

         1.必須用原生javascript代碼,騰訊網首頁那時候可不讓你引入jquery,引入一個大js這麼大的訪問量得浪費多少錢?:)
         2.就是層遮蓋的問題,輸入關鍵字 關鍵字彈層是攤在輸入框下方,那時候搜索框下面有flash,一不小心就被flash蓋住
   最坑的是還有飛來飛去的廣告
         3.其次就是後端返回的智能提示元素個數不相同,還有可能不返回,元素個數不同,就需要代理機制,冒泡到上層處理
         4.另外就是css的問題,網頁/圖片/視頻等欄目藍色框是要覆蓋的,各位看官仔細看,鼠標移動到“網頁”下拉前下面是藍色邊框,移動過去後變沒了。最先版本還記錄用戶鼠標從哪移的,點擊了那個欄目,再跟關鍵詞點擊關聯就跟複雜了。

         5. 因爲這個作爲一個模塊,就是css動態加載,動態加載簡單,加載進來能用,不跟其他人衝突,處理起來也瑣碎。

         6.與分類欄目相關的關鍵詞搜索,以及日誌記錄

         7.異常處理

          另外就是面試官說的那兩個問題。我覺得最經典的就是防抖。雖然簡單,但卻經典,稍後介紹。

 

   我做項目的那會兒,不知道是否是學藝不精,不知道有防抖的概念,那 下面就來介紹一下防抖。下面的代碼測試一下,就會浪費很多資源執行程序,這裏僅是輸出了console.log(),對資源消耗不大,如果數據庫批量查詢影響就很大了。運行下面的程序,打開瀏覽器,滾動滾動條或者放大縮小窗口,瀏覽器控制檯就會輸出一堆log。

<html>
<head>
<meta charset="utf-8" />
<style>
  body {font-size:12px; height:2000px;}
</style>
</head>
<body>
訪問頁面,打開瀏覽器控制檯,用鼠標滾動或者調整瀏覽器大小觀看效果
<script>
//滾動
window.onscroll  = function(){
	console.log('滾動條位置:' + document.body.scrollTop || document.documentElement.scrollTop)
}
//調整窗口大小
window.onresize = () => {
      console.log('窗口寬x高:' + window.innerWidth+'x'+window.innerHeight)
}
</script>
</body>
</html>

函數防抖(debounce)

防抖函數:一個頻繁觸發的操作,在規定時間內,只讓最後一次生效,前面的不生效。
  看防抖函數debounce(fn,delay),delay參數是1秒,1秒調用防抖函數不管多少次,只執行一次。

         防抖從字面的意思很好理解。典型的應用就是拍照,估計大家都有印象,不防抖的相機拍出來的照片有一堆重影。防抖就是讓相機只拍一次。

上面項目需要防抖就是鼠標在 分類區域 移動的時候,如果用戶從網頁--》圖片--》視頻一下移動到音樂,只需要記錄用戶移動到了音樂即可。還有就是輸入關鍵字非常快比如輸入 菲律賓,實際查詢菲律賓關鍵字就可以了。而不是輸入“菲”查詢一次“菲律”查詢一次 “菲律賓”查詢一次。

來一個最簡單的例子,瀏覽器執行以下代碼會發現console控制檯輸出很多次,但是計算資源是有限的,所以我們需要控制速度,這個好比遊戲打怪,不等滿血進度條完成,遊戲無法開始。

上代碼,看看防抖函數

/* 函數防抖,
@param fun  要防抖的函數名
@param delay 防抖間隔時間
*/
function debounce(fun, delay) {
    return  (args) => {
        clearTimeout(fun.id) //清除定時器
        fun.id = setTimeout(()=> {
            fun.call(this, args)
        }, delay)
    }
}

經過上面的例子想必你也清楚了,什麼是防抖,以及在前端如何防抖。對於後端那怎麼防抖呢? 有興趣的可以我在騰訊搜搜做積分系統的時候,寫過的一篇文章,用memcache或者redis實際上實現的就是防抖

http://blog.sina.com.cn/s/blog_467eb8ca0100zmvb.html

那節流又是什麼鬼東西呢? 

函數節流(throttle)

函數節流
一個函數執行一次後,只有大於設定的執行週期後纔會執行第二次。
節流函數,throttle(fun,delay)參數是1秒,則是不管持續多長時間,每秒執行一次,且每秒只執行一次。

這個打個比方,就是一盆水,慢慢流出來,而不是哐 一下直接倒了。這個很簡單可以看示例中函數節流的那部分代碼。

在上面那個 //函數節流

//函數節流
function throttle(fun,delay){
    let valid = true
    return function(args) {
       if(!valid){
           //休息時間 暫不接客
           return false
       }
       // 工作時間,執行函數並且在間隔期內把狀態位設爲無效
        valid = false
        setTimeout(() => {
            fun.call(this, args)
            valid = true;
        }, delay)
    }
}

在服務器端實現函數節流就很簡單了,最好是放在異步執行,用sleep函數定時執行即可。

關於防抖和節流的函數 ,都寫在下面了,大家在輸入框快速輸入字母,然後打開瀏覽器控制檯,自行測試。

<html>
<head>
<meta charset="utf-8" />
<style>
  body {font-size:12px;}
  span { width:60px; height:22px; display:inline-block;}
</style>
</head>
<body>
	<span class="tip">無防抖測試</span> <input id="text" />
	<br>
	<span class="tip">防抖測試</span> <input id="input" />
	<br>
	<span class="tip">節流測試</span> <input id="throttle" /> <br />

</div>
<script>
//沒有防抖請求
function ajax(content) {
  console.log('ajax request data ' + content)
}

let input = document.getElementById('text')

input.addEventListener('keyup', function (e) {
    ajax(e.target.value)
})

/*
*/

/* 函數防抖,
@param fun  要防抖的函數名
@param delay 防抖間隔時間
*/
function debounce(fun, delay) {
    return  (args) => {
        clearTimeout(fun.id) //清除定時器
        fun.id = setTimeout(()=> {
            fun.call(this, args)
        }, delay)
    }
}

let inputb = document.getElementById('input')
let debounceAjax = debounce(ajax, 1000)  //函數ajax加入防抖,間隔1秒
inputb.addEventListener('keyup', function (e) {
	debounceAjax(e.target.value)
})


//函數節流
function throttle(fun,delay){
    let valid = true
    return function(args) {
       if(!valid){
           //休息時間 暫不接客
           return false
       }
       // 工作時間,執行函數並且在間隔期內把狀態位設爲無效
        valid = false
        setTimeout(() => {
            fun.call(this, args)
            valid = true;
        }, delay)
    }
}
let throttleAjax = throttle(ajax, 1000)

let inputc = document.getElementById('throttle')
inputc.addEventListener('keyup', function(e) {
	throttleAjax(e.target.value)
})
</script>
</body>
</html>

總結

  • 函數防抖和函數節流都是防止某一時間頻繁觸發。
  • 函數防抖是某一段時間內只執行最後一次,而函數節流是小間隔時間執行一次。

結合應用場景

  • debounce 防抖 
    • window觸發resize一次
    • 滾動操作記錄最終狀態
    • 鼠標不斷點擊觸發,mousedown(單位時間內只觸發一次)
    • 輸入參數校驗,只需要校驗一次就好
  • throttle 節流
    • search搜索聯想,用戶在不斷輸入值時,節約請求資源
    • 監聽滾動事件,比如是否滑到底部自動加載更多,用throttle來判斷
    • 監聽window 的resize多次
    • 計算鼠標移動距離
    • canvas畫圖
    • 射擊遊戲,單位時間點擊按鈕,單擊鼠標發射一次子彈

 

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