系列文章: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 的博客 欢迎来访 ^_^