1. 什么是函數去抖 & 函數節流
讓某個函數在一定 事件間隔條件(去抖debounce) 或 時間間隔條件(節流throttle) 下才會去執行,避免快速多次執行函數(操作DOM,加載資源等等)給內存帶來大量的消耗從而一定程度上降低性能問題.
debounce: 當調用動作n毫秒后,才會執行該動作,若在這n毫秒內又調用此動作則將重新計算執行時間。
throttle:預先設定一個執行周期,當調用動作的時刻大於等於執行周期則執行該動作,然后進入下一個新周期。
debounce使用場景
scroll
事件(資源的加載)mousemove
事件(拖拽)resize
事件(響應式布局樣式)keyup
事件(輸入框文字停止打字后才進行校驗)
debounce電梯:
假設你正在准備乘坐電梯,並且電梯門准備關上然后上升的時候,你的同事來了,出於禮貌,我們需要停止電梯的關閉,讓同事進入.
假設源源不斷的有同事進來的話,電梯就需要處於一種待機的狀態,一直等待人員的進入,直到沒有新的同事進入或者說電梯滿了,這個時候,電梯才能運行.另外,同事的進入需要在電梯門的關閉之前,否則的話,就只能等下一趟了。
throttle使用場景
click
事件(不停快速點擊按鈕,減少觸發頻次)scroll
事件(返回頂部按鈕出現\隱藏事件觸發)keyup
事件(輸入框文字與顯示欄內容復制同步)- 減少發送ajax請求,降低請求頻率
throttle電梯:
throttle電梯不想debounce電梯一樣會無限的等待,而是我們設定一個時間,例如10s,那么10s內,其他的人可以不斷的進入電梯
但是,一旦10s過去了,那么無論如何,電梯都會進入運行的狀態。換成圖示,我們可以這么理解:
2. 實現方法&應用
首先是自己寫的各自簡易的實現,然后對比理解Lodash實現的復雜版本。看完你會發現節流本質上是去抖的一種特殊實現。
a. 簡單實現
debounce
//html <button id="btn">click</button> <div id="display"></div>
.js /** * 防反跳。fn函數在最后一次調用時刻的delay毫秒之后執行! * @param fn 執行函數 * @param delay 時間間隔 * @param isImmediate 為true,debounce會在delay時間間隔的開始時立即調用這個函數 * @returns {Function} */ function debounce(fn, delay, isImmediate) { var timer = null; //初始化timer,作為計時清除依據 return function() { var context = this; //獲取函數所在作用域this var args = arguments; //取得傳入參數 clearTimeout(timer); if(isImmediate && timer === null) { //時間間隔外立即執行 fn.apply(context,args); timer = 0; return; } timer = setTimeout(function() { fn.apply(context,args); timer = null; }, delay); } } /* 方法執行e.g. */ var btn = document.getElementById('btn'); var el = document.getElementById('display'); var init = 0; btn.addEventListener('click', debounce(function() { init++; el.innerText = init; }, 1000,true));
⇒ Demo
說明:
這里實現了一個有去抖功能的計數器。該函數接收三個參數,分別是要執行的函數fn、事件完成周期時間間隔delay(即事件間隔多少時間內不再重復觸發)以及是否在觸發周期內立即執行isImmediate。
需要注意的是要給執行函數綁定一個調用函數的上下文以及對應傳入的參數。
示例中對click事件進行了去抖,間隔時間為1000毫秒,為立即觸發方式,當不停點擊按鈕時,第一次為立即觸發,之后直到最后一次點擊事件結束間隔delay秒后開始執行加1操作。
throttle
/** * 創建並返回一個像節流閥一樣的函數,當重復調用函數的時候,最多每隔delay毫秒調用一次該函數 * @param fn 執行函數 * @param delay 時間間隔 * @returns {Function} */ function throttle(fn, delay) { var timer = null; var timeStamp = new Date(); return function() { var context = this; //獲取函數所在作用域this var args = arguments; //取得傳入參數 if(new Date()-timeStamp>delay){ timeStamp = new Date(); timer = setTimeout(function(){ fn.apply(context,args); },delay); } } } /* 方法執行 */ var btn = document.getElementById('btn'); var el = document.getElementById('display'); var init = 0; btn.addEventListener('click', throttle(function() { init++; el.innerText = init; }, 1000));
說明:
這里實現了一個簡易的有去節流功能的計數器。該函數接收兩個參數,分別是要執行的函數fn、事件完成周期時間間隔delay(即事件間隔多少時間內不再重復觸發)。需要注意的是要給執行函數綁定一個調用函數的上下文以及對應傳入的參數,以及在閉包外層的timeStamp時間記錄戳,用於判斷事件的時間間隔。示例中對click事件進行了節流,間隔時間為1000毫秒,不停點擊按鈕,計數器會間隔1秒時間進行加1操作。
缺點:
沒有控制事件的頭尾選項,即沒有控制是否在連續事件的一開始及最終位置是否需要執行。(參考underscore彌補)
⇒ Demo