上一篇文章講了去抖函數,而後這一篇講一樣爲了優化性能,下降事件處理頻率的節流函數。javascript
節流函數(throttle)就是讓事件處理函數(handler)在大於等於執行週期時才能執行,週期以內不執行,即事件一直被觸發,那麼事件將會按每小段固定時間一次的頻率執行。html
打個比方:王者榮耀、英雄聯盟、植物大戰殭屍遊戲中,技能的冷卻時間,技能的冷卻過程當中,是沒法使用技能的,只能等冷卻時間到以後才能執行。java
那什麼樣的場景能用到節流函數呢?
好比:數組
咱們經過一個簡單的示意來理解:
閉包
節流函數能夠用時間戳和定時器兩種方式進行處理。app
<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>
複製代碼
看時間戳實現版本的效果:
異步
<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>
複製代碼
看看定時器版實現版本的效果: 函數
對比時間戳和定時器兩種方式,效果上的區別主要在於:工具
事件戳方式會當即執行,定時器會在事件觸發後延遲執行,並且事件中止觸發後還會再延遲執行一次。post
具體選擇哪一種方式取決於使用場景。underscore 把這兩類場景用 leading 和 trailing 進行了表示。
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>
複製代碼
下面是我畫的示意圖:
大體總結一下代碼對事件處理邏輯的影響:節流和去抖的常見場景
去抖和節流函數都是爲了下降高頻率事件觸發的事件處理頻率,從而優化網頁中大量重繪重排帶來的性能問題。
其區別在於去抖會在高頻率事件觸發時,只執行一次,節流會在知足間隔時間後執行一次。去抖的 immediate,節流中的 leading, trailing 都是爲了儘量知足這類工具函數的不一樣使用場景。
歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。