如下場景每每因爲事件頻繁被觸發,於是頻繁執行DOM操做、資源加載等重行爲,致使UI停頓甚至瀏覽器崩潰。html
1. window對象的resize、scroll事件瀏覽器
2. 拖拽時的mousemove事件app
3. 射擊遊戲中的mousedown、keydown事件函數
4. 文字輸入、自動完成的keyup事件性能
實際上對於window的resize事件,實際需求大多爲中止改變大小n毫秒後執行後續處理;而其餘事件大多的需求是以必定的頻率執行後續處理。針對這兩種需求就出現了debounce和throttle兩種解決辦法。this
什麼是debounce spa
1. 定義code
若是用手指一直按住一個彈簧,它將不會彈起直到你鬆手爲止。htm
也就是說當調用動做n毫秒後,纔會執行該動做,若在這n毫秒內又調用此動做則將從新計算執行時間。對象
接口定義:
/** * 空閒控制 返回函數連續調用時,空閒時間必須大於或等於 idle,action 纔會執行 * @param idle {number} 空閒時間,單位毫秒 * @param action {function} 請求關聯函數,實際應用須要調用的函數 * @return {function} 返回客戶調用函數 */ debounce(idle,action)
2. 簡單實現
var debounce = function(idle, action){ var last return function(){ var ctx = this, args = arguments clearTimeout(last) last = setTimeout(function(){ action.apply(ctx, args) }, idle) } }
btw 題外話 對此處的簡單實現理解有困難能夠結合http://www.javashuo.com/article/p-tgtuhijl-y.html 南辰的簡單實現進行理解,邏輯是一致的,只是大佬的代碼寫的太精簡了,不助於理解。
什麼是throttle
1. 定義
若是將水龍頭擰緊直到水是以水滴的形式流出,那你會發現每隔一段時間,就會有一滴水流出。
也就是會說預先設定一個執行週期,當調用動做的時刻大於等於執行週期則執行該動做,而後進入下一個新週期。
接口定義:
/** * 頻率控制 返回函數連續調用時,action 執行頻率限定爲 次 / delay * @param delay {number} 延遲時間,單位毫秒 * @param action {function} 請求關聯函數,實際應用須要調用的函數 * @return {function} 返回客戶調用函數 */ throttle(delay,action)
2. 簡單實現
var throttle = function(delay, action){ var last = 0
return function(){ var curr = +new Date() if (curr - last > delay){ action.apply(this, arguments) last = curr } } }
underscore v1.7.0相關的源碼剖析
1. _.throttle函數
_.throttle = function(func, wait, options) { /* options的默認值 * 表示首次調用返回值方法時,會立刻調用func;不然僅會記錄當前時刻,當第二次調用的時間間隔超過wait時,才調用func。 * options.leading = true; * 表示當調用方法時,未到達wait指定的時間間隔,則啓動計時器延遲調用func函數,若後續在既未達到wait指定的時間間隔和func函數又未被調用的狀況下調用返回值方法,則被調用請求將被丟棄。 * options.trailing = true; * 注意:當options.trailing = false時,效果與上面的簡單實現效果相同 */ var context, args, result; var timeout = null; 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; }; return function() { var now = _.now(); if (!previous && options.leading === false) previous = now; // 計算剩餘時間 var remaining = wait - (now - previous); context = this; args = arguments; // 當到達wait指定的時間間隔,則調用func函數 // 精彩之處:按理來講remaining <= 0已經足夠證實已經到達wait的時間間隔,但這裏還考慮到假如客戶端修改了系統時間則立刻執行func函數。 if (remaining <= 0 || remaining > wait) { // 因爲setTimeout存在最小時間精度問題,所以會存在到達wait的時間間隔,但以前設置的setTimeout操做還沒被執行,所以爲保險起見,這裏先清理setTimeout操做 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // options.trailing=true時,延時執行func函數 timeout = setTimeout(later, remaining); } return result; }; };
2. _.debounce函數
_.debounce = function(func, wait, immediate) { // immediate默認爲false var timeout, args, context, timestamp, result; var later = function() { // 當wait指定的時間間隔期間屢次調用_.debounce返回的函數,則會不斷更新timestamp的值,致使last < wait && last >= 0一直爲true,從而不斷啓動新的計時器延時執行func var last = _.now() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); // 第一次調用該方法時,且immediate爲true,則調用func函數 var callNow = immediate && !timeout; // 在wait指定的時間間隔內首次調用該方法,則啓動計時器定時調用func函數 if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; };
throttle和debounce均是經過減小實際邏輯處理過程的執行來提升事件處理函數運行性能的手段,並無實質上減小事件的觸發次數。二者在概念理解上確實比較容易使人混淆,結合各js庫的具體實現進行理解效果將會更好。