JS專題之節流函數

上一篇文章講了去抖函數,而後這一篇講一樣爲了優化性能,下降事件處理頻率的節流函數。javascript

1、什麼是節流?

節流函數(throttle)就是讓事件處理函數(handler)在大於等於執行週期時才能執行,週期以內不執行,即事件一直被觸發,那麼事件將會按每小段固定時間一次的頻率執行。html

打個比方:王者榮耀、英雄聯盟、植物大戰殭屍遊戲中,技能的冷卻時間,技能的冷卻過程當中,是沒法使用技能的,只能等冷卻時間到以後才能執行。java

那什麼樣的場景能用到節流函數呢?
好比:數組

  1. 頁面滾動和改變大小時須要進行業務處理,好比判斷是否滑到底部,而後進行懶加載數據。
  2. 按鈕被高頻率地點擊時,好比遊戲和搶購網站。

咱們經過一個簡單的示意來理解:
閉包

節流函數能夠用時間戳和定時器兩種方式進行處理。app

2、時間戳方式實現

<div class="container" id="container">
    正在滑動:0
</div>

<script> window.onload = function() { var bodyEl = document.getElementsByTagName("body")[0] } var count = 0; window.onmousemove = throttle(eventHandler, 1000); function eventHandler(e) { var containerEl = document.getElementById("container"); containerEl.innerHTML = "正在滑動: " + count; count++; } function throttle(func, delay) { var delay = delay || 1000; var previousDate = new Date(); var previous = previousDate.getTime(); // 初始化一個時間,也做爲高頻率事件判斷事件間隔的變量,經過閉包進行保存。 return function(args) { var context = this; var nowDate = new Date(); var now = nowDate.getTime(); if (now - previous >= delay) { // 若是本次觸發和上次觸發的時間間隔超過設定的時間 func.call(context, args); // 就執行事件處理函數 (eventHandler) previous = now; // 而後將本次的觸發時間,做爲下次觸發事件的參考時間。 } } } </script>
複製代碼

看時間戳實現版本的效果:
異步

3、定時器方式實現

<div class="container" id="container">
    正在滑動: 0
</div>

<script> window.onload = function() { var bodyEl = document.getElementsByTagName("body")[0] } var count = 0; window.onmousemove = throttle(eventHandler, 1000); function eventHandler(e) { var containerEl = document.getElementById("container"); containerEl.innerHTML = "正在滑動: " + count; count++; } function throttle(func, delay) { var delay = delay || 1000; var timer = null; return function(args) { var context = this; var nowDate = new Date(); var now = nowDate.getTime(); if (!timer) { timer = setTimeout(function() { func.call(context, args); timer = null; }, delay) } } } </script>
複製代碼

看看定時器版實現版本的效果: 函數

3、時間戳和定時器的對比分析

對比時間戳和定時器兩種方式,效果上的區別主要在於:工具

事件戳方式會當即執行,定時器會在事件觸發後延遲執行,並且事件中止觸發後還會再延遲執行一次。post

具體選擇哪一種方式取決於使用場景。underscore 把這兩類場景用 leading 和 trailing 進行了表示。

4、underscore 源碼實現

underscore 的源碼中就同時實現了時間戳和定時器實現方式,在調用時能夠自由選擇要不要在間隔時間開始時(leading)執行,或是間隔時間結束後(trailing)執行。

具體看僞代碼和示意圖:

<div class="container" id="container">
        正在滑動: 0
    </div>
    <div class="height"></div>
    <script> window.onload = function() { var bodyEl = document.getElementsByTagName("body")[0] } var count = 0; // 事件處理函數 function eventHandler(e) { var containerEl = document.getElementById("container"); containerEl.innerHTML = "正在滑動: " + count; count++; } var _throttle = function(func, wait, options) { var context, args, result; // 定時器變量默認爲 null, 是爲了若是想要觸發了一次後再延遲執行一次。 var timeout = null; // 上一次觸發事件回調的時間戳。 默認爲 0 是爲了方便第一次觸發默認當即執行 var previous = 0; // 若是沒有傳入 options 參數 // 則將 options 參數置爲空對象 if (!options) options = {}; var later = function() { // 若是 options.leading === false // 則每次觸發回調後將 previous 置爲 0, 表示下次事件觸發會當即執行事件處理函數 // 不然置爲當前時間戳 previous = options.leading === false ? 0 : +new Date(); // 剩餘時間跑完,執行事件,並把定時器變量置爲空,若是不爲空,那麼剩餘時間內是不會執行事件處理函數的,見 else if 那。 timeout = null; result = func.apply(context, args); // 剩餘時間結束,並執行完事件後,清理閉包中自由變量的內存垃圾,由於再也不須要了。 if (!timeout) context = args = null; }; // 返回的事件回調函數 return function() { // 記錄當前時間戳 var now = +new Date(); // 第一次執行回調(此時 previous 爲 0,以後 previous 值爲上一次時間戳) // 而且若是程序設定第一個回調不是當即執行的(options.leading === false) // 則將 previous 值(表示上次執行的時間戳)設爲 now 的時間戳(第一次觸發時) // 表示剛執行過,此次就不用執行了 if (!previous && options.leading === false) previous = now; // 間隔時間 和 上一次到本次事件觸發回調的持續時間的時間差 var remaining = wait - (now - previous); context = this; args = arguments; // 若是間隔時間還沒跑完,則不會執行任何事件處理函數。 // 若是超過間隔時間,就能夠觸發方法(remaining <= 0) // remaining > wait,表示客戶端系統時間被調整過 // 也會當即執行 func 函數 if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); // 解除引用,防止內存泄露 timeout = null; } // 重置前一次觸發的時間戳 previous = now; // result 爲事件處理函數(handler)的返回值 // 採用 apply 傳遞類數組對象 arguments result = func.apply(context, args); // 引用置爲空,防止內存泄露 if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 若是 remaining > 0, 表示在間隔時間內,又觸發了一次事件 // 若是 trailing 爲真,則會在間隔時間結束時執行一次事件處理函數(handler) // 在從觸發到剩餘時間跑完,會利用一個定時器執行事件處理函數,並在定時器結束時把 定時器變量置爲空 // 若是剩餘事件內已經存在一個定時器,則不會進入本 else if 分支, 表示剩餘時間已經有一個定時器在運行,該定時器會在剩餘時間跑完後執行。 // 若是 trailing = false,即不須要在剩餘時間跑完執行事件處理函數。 // 間隔 remaining milliseconds 後觸發 later 方法 timeout = setTimeout(later, remaining); } // 回調返回值 return result; }; }; window.onmousemove = _throttle(eventHandler, 1000); </script>
複製代碼

下面是我畫的示意圖:

大體總結一下代碼對事件處理邏輯的影響:

  1. 若是 leading 爲真,那麼綠色意味着間隔時間的開始會當即執行,第一次觸發也會當即執行。
  2. 若是 trailing 爲真,那麼從藍紫色的豎細線後的剩餘事件,會跑一個定時器,定時器在時間間隔結束時再執行一次事件處理函數。
  3. 若是 leading 不爲真,那麼第一次事件觸發不會當即執行。
  4. 若是 trailing 不爲真,最後一次事件觸發後,否則再執行一次事件處理函數。

節流和去抖的常見場景

  1. 輸入框打字輸入完後纔開始異步請求數據校驗內容:去抖
  2. 下拉滾動條判斷是否到底部,進行懶加載數據:去抖和節流均可以,判斷是否到底的方式不一樣
  3. 活動網站、遊戲網站,按鈕被瘋狂點擊:節流

5、總結

去抖和節流函數都是爲了下降高頻率事件觸發的事件處理頻率,從而優化網頁中大量重繪重排帶來的性能問題。

其區別在於去抖會在高頻率事件觸發時,只執行一次,節流會在知足間隔時間後執行一次。去抖的 immediate,節流中的 leading, trailing 都是爲了儘量知足這類工具函數的不一樣使用場景。

歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。

掘金專欄 JavaScript 系列文章

  1. JavaScript之變量及做用域
  2. JavaScript之聲明提高
  3. JavaScript之執行上下文
  4. JavaScript之變量對象
  5. JavaScript之原型與原型鏈
  6. JavaScript之做用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中完全理解this
  12. JavaScript專題之模擬實現call和apply
  13. JavaScript專題之模擬實現bind
  14. JavaScript專題之模擬實現new
  15. JS專題之事件模型
  16. JS專題之事件循環
  17. JS專題之去抖函數
相關文章
相關標籤/搜索