爲了不某個事件在較短的時間段內(稱爲 T)內連續觸發從而引發的其對應的事件處理函數沒必要要的連續執行的一種事件處理機制(高頻觸發事件解決方案)
debounce:當調用動做觸發一段時間後,纔會執行該動做,若在這段時間間隔內又調用此動做則將從新計算時間間隔。(屢次連續事件觸發動做/最後一次觸發以後的指定時間間隔執行回調函數)
throttle:預先設定一個執行週期,當調用動做的時刻大於等於執行週期則執行該動做,而後進入下一個新的時間週期。(每一個指定時間執行一次回調函數,能夠指定時間間隔以前調用)javascript
一、throttle 保證了在每一個 T 內至少執行一次,而 debounce 沒有這樣的保證
二、每次事件觸發時參考的時間點
,對於debounce
來是上一次事件觸發的時間
而且在延時沒有結束時會重置延時;throttle
是上一次 handler 執行的時間
而且在延時還沒有結束時不會重置延時java
響應速度跟不上觸發頻率,每每會出現延遲,致使假死或者卡頓感git
空閒控制:全部操做最後一次性執行github
【簡潔版】閉包
/** * @param fn {Function} 實際要執行的函數 * @param delay {Number} 延遲時間,也就是閾值,單位是毫秒(ms) * @return {Function} 返回一個「去彈跳」了的函數 */ function debounce(fn, delay) { // 定時器,用來 setTimeout var timer // 返回一個函數,這個函數會在一個時間區間結束後的 delay 毫秒時執行 fn 函數 return function () { // 保存函數調用時的上下文和參數,傳遞給 fn var context = this var args = arguments // 每次這個返回的函數被調用,就清除定時器,以保證不執行 fn clearTimeout(timer) // 當返回的函數被最後一次調用後(也就是用戶中止了某個連續的操做), // 再過 delay 毫秒就執行 fn timer = setTimeout(function () { fn.apply(context, args) }, delay) } }
【完整版】app
// immediate: 是否當即執行回調函數; 其它參數同上 function debounce(fn, wait, immediate) { let timer = null return function() { let args = [].slice.call(arguments) if (immediate && !timer) { fn.apply(this, args) } if (timer) clearTimeout(timer) timer = setTimeout(() => { //箭頭函數,this指向外層環境 fn.apply(this, args) }, wait) } }
// 測試: var fn = function() { console.log('debounce..') } oDiv.addEventListener('click', debounce(fn, 3000))
固定頻次:減小執行頻次,每隔必定時間執行一次函數
【簡潔版】測試
/** * 固定回調函數執行的頻次 * @param fn {Function} 實際要執行的函數 * @param interval {Number} 執行間隔,單位是毫秒(ms) * * @return {Function} 返回一個「節流」函數 */ var throttle = function (fn, interval) { // 記錄前一次時間 var last = +new Date() var timer = null // 包裝完後返回 閉包函數 return function () { var current = +new Date() var args = [].slice.call(arguments, 0) var context = this // 首先清除定時器 clearTimeout(timer) // current 與last 間隔大於interval 執行一次fn // 在一個週期內 last相對固定 current一直再增長 // 這裏能夠保證調用很密集的狀況下 current和last 必須是相隔interval 纔會調用fn if (current - last >= interval) { fn.apply(context, args) last = current } else { // 若是沒有大於間隔 添加定時器 // 這能夠保證 即便後面沒有再次觸發 fn也會在規定的interval後被調用 timer = setTimeout(function() { fn.apply(context, args) last = current }, interval-(current - last)) } } }
【完整版】this
/** * 頻率控制 返回函數連續調用時,func 執行頻率限定爲 次 / wait * 自動合併 data * * 若無 option 選項,或者同時爲true,即 option.trailing !== false && option.leading !== false,在固定時間開始時刻調用一次回調,並每一個固定時間最後時刻調用回調 * 若 option.trailing !== false && option.leading === false, 每一個固定時間最後時刻調用回調 * 若 option.trailing === false && option.leading !== false, 只會在固定時間開始時刻調用一次回調 * 若同時爲false 則不會被調用 * * @param {function} func 傳入函數 * @param {number} wait 表示時間窗口的間隔 * @param {object} options 若是想忽略開始邊界上的調用,傳入{leading: false}。默認undefined * 若是想忽略結尾邊界上的調用,傳入{trailing: false}, 默認undefined * @return {function} 返回客戶調用函數 */ function throttle (func, wait, options) { var context, args, result; var timeout = null; // 上次執行時間點 var previous = 0; if (!options) { options = {}; } // 延遲執行函數 function later () { // 若設定了開始邊界不執行選項,上次執行時間始終爲0 previous = options.leading === false ? 0 : Date.now(); timeout = null; result = func.apply(context, args); if (!timeout) { context = args = null; } } return function (handle, data) { var now = Date.now(); // 首次執行時,若是設定了開始邊界不執行選項,將上次執行時間設定爲當前時間。 if (!previous && options.leading === false) { previous = now; } // 延遲執行時間間隔 var remaining = wait - (now - previous); context = this; args = args ? [handle, Object.assign(args[1], data)] : [handle, data]; // 延遲時間間隔remaining小於等於0,表示上次執行至此所間隔時間已經超過一個時間窗口 // remaining大於時間窗口wait,表示客戶端系統時間被調整過 if (remaining <= 0 || remaining > wait) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); if (!timeout) { context = args = null; } // 若是延遲執行不存在,且沒有設定結尾邊界不執行選項 } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result } }
前二者 debounce 和 throttle 均可以按需使用;後二者確定是用 throttle.net
_.debounce = function(func, wait, immediate) { var timeout, result; var later = function(context, args) { timeout = null; if (args) result = func.apply(context, args); }; var debounced = restArgs(function(args) { if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { timeout = _.delay(later, wait, this, args); } return result; }); debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; };
_.throttle = function(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; };
【參考】
https://blog.coding.net/blog/...
https://github.com/lishengzxc...