JS防抖和節流

對防抖和節流的一些理解,作一次記錄。(以前項目中的需求是在輸入框中輸入內容以後,調接口返回值,而後不知道還有節流這波操做,而後就寫了判斷當鼠標失去焦點的時候調接口,後來大佬說可使用節流來實現)前端

防抖和節流算起來應該屬於性能優化的知識,可是處理不當或者是聽任無論就容易引發瀏覽器卡死。就是在綁定scroll、resize這類事件時,當他發生時,被觸發的頻率很是高,間隔很近。若是事件中涉及到大量的位置計算、DOM操做、元素重繪等工做且這些工做沒法在下一個scroll事件觸發前完成,就會形成瀏覽器調幀。加之用戶鼠標滾動每每時連續的,就會持續觸發scroll事件致使調幀擴大、瀏覽器CPU使用率增長、用戶體驗受到影響。尤爲時在涉及與後端的交互中,前端依賴於某中事件如resize、scroll,發送http請求,在這個過程當中,若是不作防抖處理,那麼在事件觸發的一瞬間,就會有不少個請求發過去,增長了服務端的壓力。ajax

1.從滾動條監聽的例子提及

先說一個常見的功能,不少網站會提供一個按鈕:用於返回頂部。chrome

這個按鈕只會在滾動到距離頂部必定位置的時候纔會出現,那麼如今抽象出這個功能需求 --- 監聽滾動條事件,返回當前滾條和頂部的距離。後端

這個需求很簡單,直接寫:瀏覽器

1 function showTop  () {
2     var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
3   console.log('滾動條位置:' + scrollTop);
4 }
5 window.onscroll  = showTop
View Code

可是:在運行的時候會發現:這個函數的默認執行頻率過高了!以chrome爲例,咱們能夠點擊選中一個頁面的滾動條,而後點擊一次鍵盤的【向下方向鍵】,會發現函數執行了8-9次性能優化

然而實際上並不須要如此高頻的反饋,畢竟瀏覽器的性能是有限的,不該該浪費在這裏,因此須要優化這種場景。閉包

2.防抖

基於上述的場景,首先提出第一種思路:在第一次觸發事件時,不當即執行函數,而是給出一個期限值:300msapp

  • 若是在300ms內沒有再次觸發滾動事件,那麼就執行函數。dom

  • 若是在300ms內再次觸發滾動事件,那麼當前的即便取消,從新開始計時。ide

效果就是:若是在短期內大量觸發贊成事件,只會執行一次函數。

實現:既然前面都提到了計時,那實現的關鍵就在於setTimeOut這個函數,因爲還須要一個變量來保存計時,考慮維護全局純淨,能夠藉助閉包來實現:

 1 /**
 2 * fn[function] 須要防抖的函數
 3 * delay[number] 毫秒,防抖期限值
 4 */
 5 function debounce(fn,delay){
 6   let timer = null;
 7   return function(){
 8     if(timer){
 9       //進入該分支語句,說明當前正在一個計時過程當中,而且又觸發了相同事件。因此要取消當前的計時,從新開始計時
10       clearTimeout(timer)
11       timer = setTimeOut(fn,delay)
12     }else{
13       // 進入該分支說明當前並無在計時,那麼就開始一個計時
14       timer = setTimeOut(fn,delay)
15     }
16   }
17 }
View Code

固然 上述代碼是爲了貼合思路,方便理解。寫完會發現其實timer = setTimeOut(fn,delay)是必定會執行的,因此能夠稍微簡化下:

 1 function debounce(fn,delay){
 2     let timer = null //藉助閉包
 3     return function() {
 4         if(timer){
 5             clearTimeout(timer) 
 6         }
 7         timer = setTimeout(fn,delay) // 簡化寫法
 8     }
 9 }
10 // 而後是舊代碼
11 function showTop  () {
12     var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
13   console.log('滾動條位置:' + scrollTop);
14 }
15 window.onscroll = debounce(showTop,1000) // 爲了方便觀察效果咱們取個大點的間斷值,實際使用根據須要來配置
View Code

此時會發現,必須在中止滾動1s之後,纔會打印出滾動條位置。

防抖也就實現了:定義即:

  • 對於短期內連續觸發的事件(上面的滾動事件),防抖的含義就是讓某個時間期限(如上面的1000毫秒)內,事件處理函數只執行一次。

3.節流

繼續思考,使用上面的防抖方案來處理問題的結果是:

  • 若是在限定時間段內,不斷觸發滾動事件(好比某個用戶閒着無聊,按住滾動不斷的拖來拖去),只要不中止觸發,理論上就永遠不會輸出當前距離頂部的距離。

可是若是產品同窗的指望處理方案是:即便用戶不斷拖動滾動條,也能在某個時間間隔以後給出反饋呢?

其實很簡單:咱們能夠設計一種相似控制閥門同樣按期開放的函數,也就是讓函數執行一次後,在某個時間段內暫時失效,過了這段時間後再從新激活(相似於技能冷卻時間)。

效果:若是短期內大量觸發同一事件,那麼在函數執行一次以後,該函數在指定的時間期限內再也不工做,直至過了這段時間才從新生效。

實現 這裏藉助setTimeout來作一個簡單的實現,加上一個狀態位valid來表示當前函數是否處於工做狀態:

 

定時器方案

 1 function throttle(fn,delay){
 2   let valid = true;
 3   return function(){
 4        if(!valid){
 5          return false;
 6         }
 7        //執行函數而且在間隔期間內把狀態位設爲無效
 8            valid = false;
 9         setTimeout(()=>{
10           fn()
11           valid = true;
12         },delay)
13     }
14 }
15 /* 請注意,節流函數並不止上面這種實現方案,
16    例如能夠徹底不借助setTimeout,能夠把狀態位換成時間戳,而後利用時間戳差值是否大於指定間隔時間來作斷定。
17    也能夠直接將setTimeout的返回的標記當作判斷條件-判斷當前定時器是否存在,若是存在表示還在冷卻,而且在執行fn以後消除定時器表示激活,原理都同樣
18     */
19 // 如下照舊
20 function showTop  () {
21     var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
22   console.log('滾動條位置:' + scrollTop);
23 }
24 window.onscroll = throttle(showTop,1000) 
View Code

運行以上代碼的結果:

若是一直拖着滾動條進行滾動,那麼會以1s的時間間隔,持續輸出當前位置和頂部的距離。

時間戳方案

 1 var throttle = function(fn,delay){
 2   var prev = Date.now();
 3   return function(){
 4     var context = this;
 5     var args = arguments;
 6     var now = Date.now();
 7     if(now -prev >=delay){
 8        fn.apply(context,args)
 9        prev = Date.now();
10        }
11   }
12 }
13 function handle(){
14   console.log(Math.random());
15 }
16 window.addEventListener('scroll',throttle(handle,1000));
View Code

時間戳+定時器

 1 var throttle = function(func, delay) {
 2      var timer = null;
 3      var startTime = Date.now();
 4      return function() {
 5              var curTime = Date.now();
 6              var remaining = delay - (curTime - startTime);
 7              var context = this;
 8              var args = arguments;
 9              clearTimeout(timer);
10               if (remaining <= 0) {
11                     func.apply(context, args);
12                     startTime = Date.now();
13               } else {
14                     timer = setTimeout(func, remaining);
15               }
16       }
17 }
18 function handle() {
19       console.log(Math.random());
20 }
21  window.addEventListener('scroll', throttle(handle, 1000));
View Code

4.其餘應用場景舉例

講完了這兩個技巧,下面介紹一下平時開發中常遇到的場景:

  1. 搜索框input事件,例如要支持輸入實時搜索可使用節流方案(間隔一段時間就必須查詢相關內容),或者實現輸入間隔大於某個值(如500ms),就當作用戶輸入完成,而後開始搜索,具體使用哪一種方案要看業務需求。

  2. 頁面resize事件,常見於須要作頁面適配的時候。須要根據最終呈現的頁面狀況進行dom渲染(這種情形通常是使用防抖,由於只須要判斷最後一次的變化狀況)

5.總結

函數防抖:將幾回操做合併爲一個操做進行。原理是維護一個計時器,規定在delay時間後觸發函數,可是在delay內再次觸發的話,就會取消以前的計時器而從新設置。這樣一來。只有最後一次操做能被觸發。

函數節流:使得必定時間內只觸發一次函數,原理是經過判斷是否到達必定時間來觸發函數。

區別:函數節流無論事件觸發多頻繁,都會保證在規定的時間內必定會執行一次真正的事件處理函數,而函數防抖只是在最後一次事件後才觸發一次函數。好比在頁面的無限加載場景下,須要用戶在滾動頁面時,每隔一段時間發一次ajax請求,而不是在啊用戶停下滾動頁面操做時纔去請求數據。這種場景就適合用節流技術來實現。

相關文章
相關標籤/搜索