系列文章:javascript
上一篇文章中介紹的屬性描述符的知識太偏於理論,今天閱讀的 throttle-debounce 模塊會實用許多,在工做經常能夠用到。java
今天閱讀的 npm 模塊是 throttle-debounce,它提供了 throttle
和 debounce
兩個函數:throttle 的含義是節流,debounce 的含義是防抖動,經過它們能夠限制函數的執行頻率,避免短期內函數屢次執行形成性能問題,當前包版本爲 2.0.1,周下載量爲 6.3 萬。git
首選須要介紹一下 throttle
和 debounce
,它們均可以用於 函數節流 從而提高性能,但它們仍是存在一些不一樣:github
雖然天天最煩等電梯要花上十幾分鍾,但仍是能夠用坐電梯來舉例子:npm
從上面兩個例子中能夠看出二者最大的區別在於只要有事件發生(有人想坐電梯),若使用了 throttle
方法,那麼在一段時間內事件響應函數必定會執行(30秒內我按下關門鍵);若使用了 debounce
方法,那麼只有事件中止發生後(我發現沒有人想坐電梯)纔會執行。segmentfault
你們能夠嘗試在下面的 Demo 中滾動鼠標直觀地感覺到這二者的不一樣:瀏覽器
See the Pen The Difference Between Throttling, Debouncing, and Neither by Elvin Peng (@elvinn) on CodePen.服務器
對於 throttle-debounce,它的簡單用法以下:閉包
import { throttle, debounce } from 'throttle-debounce';
function foo() { console.log('foo..'); }
function bar() { console.log('bar..'); }
const fooWrapper = throttle(200, foo);
for (let i = 1; i < 10; i++) {
setTimeout(fooWrapper, i * 30);
}
// => foo 執行了三次
// => foo..
// => foo..
// => foo..
const barWrapper = debounce(200, bar);
for (let i = 1; i < 10; i++) {
setTimeout(barWrapper, i * 30);
}
// => bar 執行了一次
// => bar..
複製代碼
將源碼簡化後適當修改以下:app
// 源碼 4-1
function throttle(delay, callback) {
let timeoutID;
let lastExec = 0;
function wrapper() {
const self = this;
const elapsed = Number(new Date()) - lastExec;
const args = arguments;
function exec() {
lastExec = Number(new Date());
callback.apply(self, args);
}
clearTimeout(timeoutID);
if (elapsed > delay) {
exec();
} else {
timeoutID = setTimeout(exec, delay - elapsed);
}
}
return wrapper;
}
複製代碼
整個代碼的邏輯十分清晰,一共只有三步:
elapsed
,並清除以前設置的計時器。delay
,那麼當即執行函數,並更新最近一次函數的執行時間。delay
,那麼經過 setTimeout
設置一個計數器,讓函數在 delay - elapsed
時間後執行。源碼 4-1 並不難理解,不過須要關注一下 this
的使用:
function throttle(delay, callback) {
// ...
function wrapper() {
const self = this;
const args = arguments;
// ...
function exec() {
// ...
callback.apply(self, args);
}
}
}
複製代碼
在上面的代碼中,經過 self
變量臨時保存 this
的值,從而在 exec
函數中經過 callback.apply(self, args)
傳入正確的 this
值,這種作法在閉包相關的函數調用中十分經常使用。正由於這裏對 this
的處理,因此能夠實現下面的能力:
function foo() { console.log(this.name); }
const fooWithName = throttle(200, foo);
const obj = {name: 'elvin'};
fooWithName.call(obj, 'elvin');
// => 'elvin'
複製代碼
因爲 debounce
n 只是日後推延函數的執行時間,並不具備 throttle
每隔一段時間必定會執行的能力,因此其實現起來更加簡單:
function debounce(delay, callback) {
let timeoutID;
function wrapper() {
const self = this;
const args = arguments;
function exec() {
callback.apply(self, args);
}
clearTimeout(timeoutID);
timeoutID = setTimeout(exec, delay);
}
return wrapper;
}
複製代碼
將上述代碼與 throttle
實現的代碼相比,能夠發現其就是去除了 elapsed
相關邏輯後的代碼,其他大部分代碼如出一轍,因此 debounce
函數能夠藉助 throttle
函數實現(throttle-debounce 源代碼中也是這樣作的),throttle
函數也能夠藉助 debounce
函數實現。
throttle
和 debounce
適用於用戶短期內頻繁執行某一相同操做的場景,例如:
resize
事件。mousemove
等事件。keydown
| keypress
| keyinput
| keyup
等事件。scroll
事件。click
事件。在網上搜索了很多資料,發現對兩個函數的使用場景有時彼此之間都互相矛盾,例若有的說在搜索框進行輸入,應該使用 debounce
進行限流,從而減輕服務器壓力;有的說使用 throttle
進行限流便可,能夠更快地返回用戶的搜索結果。
在我看來,並不存在一個場景,就必定是使用 throttle
和 debounce
中的一種方法並另一種方法好,每每須要結合自身的狀況進行考慮和選擇:
throttle
進行限流帶來更好的用戶體驗。debounce
進行更強力的限流,從而減輕壓力。throttle-debounce 源碼和我前幾天所看的 Sindre 所寫的模塊代碼風格徹底不一樣,它的代碼中註釋的行數約爲代碼行數的三倍,並且函數的參數均有詳細的註釋,這本應是一件好事,可是對於我閱讀源碼而言,並無以爲更加輕鬆,而求因爲對可選參數進行的以下處理,讓我閱讀起來更加費力:
// 源碼 4-2
/** * * @param {Number} delay * @param {Boolean} [noTrailing] * @param {Function} callback * @param {Boolean} [debounceMode] * * @return {Function} A new, throttled, function. */
export default function ( delay, noTrailing, callback, debounceMode ) {
// `noTrailing` defaults to falsy.
if ( typeof noTrailing !== 'boolean' ) {
debounceMode = callback;
callback = noTrailing;
noTrailing = undefined;
}
// ...
}
複製代碼
在源碼 4-2 中,從註釋能夠看出 noTrailing
和 debounceMode
是可選參數,delay
和 callback 爲必選參數,而後它將可選參數 noTrailing
放在了必選參數 callback
以前,再在函數中的代碼進行判斷:假如 noTrailing
爲函數的話,則此值應做爲 callback
,而後再將 noTrailing
設爲默認值 undefined
。
不由感嘆這真是一番騷操做,哪怕是爲了兼容 ES5,也有更好的寫法,這裏說說我我的認爲可用 ES6 語法時更好的寫法:
export default function (dalay, noTrailing, options = { callback = false, debounceMode = false, } = {}) {
// ...
}
複製代碼
關於我:畢業於華科,工做在騰訊,elvin 的博客 歡迎來訪 ^_^