JavaScript 专题系列第二篇,讲解节流,带你从零实现一个 underscore 的 throttle 函数git
在《JavaScript专题之跟着underscore学防抖》中,咱们了解了为何要限制事件的频繁触发,以及如何作限制:github
今天重点讲讲节流的实现。app
节流的原理很简单:函数
若是你持续触发事件,每隔一段时间,只执行一次事件。优化
根据首次是否执行以及结束后是否执行,效果有所不一样,实现的方式也有所不一样。
咱们用 leading 表明首次是否执行,trailing 表明结束后是否再执行一次。ui
关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。this
让咱们来看第一种方法:使用时间戳,当触发事件的时候,咱们取出当前的时间戳,而后减去以前的时间戳(最一开始值设为 0 ),若是大于设置的时间周期,就执行函数,而后更新时间戳为当前的时间戳,若是小于,就不执行。spa
看了这个表述,是否是感受已经能够写出代码了…… 让咱们来写初版的代码:code
// 初版
function throttle(func, wait) {
var context, args;
var previous = 0;
return function() {
var now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}复制代码
例子依然是用讲 debounce 中的例子,若是你要使用:cdn
container.onmousemove = throttle(getUserAction, 1000);复制代码
效果演示以下:
咱们能够看到:当鼠标移入的时候,事件马上执行,每过 1s 会执行一次,若是在 4.2s 中止触发,之后不会再执行事件。
接下来,咱们讲讲第二种实现方式,使用定时器。
当触发事件的时候,咱们设置一个定时器,再触发事件的时候,若是定时器存在,就不执行,直到定时器执行,而后执行函数,清空定时器,这样就能够设置下个定时器。
// 第二版
function throttle(func, wait) {
var timeout;
var previous = 0;
return function() {
context = this;
args = arguments;
if (!timeout) {
timeout = setTimeout(function(){
timeout = null;
func.apply(context, args)
}, wait)
}
}
}复制代码
为了让效果更加明显,咱们设置 wait 的时间为 3s,效果演示以下:
咱们能够看到:当鼠标移入的时候,事件不会马上执行,晃了 3s 后终于执行了一次,此后每 3s 执行一次,当数字显示为 3 的时候,马上移出鼠标,至关于大约 9.2s 的时候中止触发,可是依然会在第 12s 的时候执行一次事件。
因此比较两个方法:
那咱们想要一个什么样的呢?
有人就说了:我想要一个有头有尾的!就是鼠标移入能马上执行,中止触发的时候还能再执行一次!
因此咱们综合二者的优点,而后双剑合璧,写一版代码:
// 第三版
function throttle(func, wait) {
var timeout, context, args, result;
var previous = 0;
var later = function() {
previous = +new Date();
timeout = null;
func.apply(context, args)
};
var throttled = function() {
var now = +new Date();
//下次触发 func 剩余的时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 若是没有剩余的时间了或者你改了系统时间
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}复制代码
效果演示以下:
咱们能够看到:鼠标移入,事件马上执行,晃了 3s,事件再一次执行,当数字变成 3 的时候,也就是 6s 后,咱们马上移出鼠标,中止触发事件,9s 的时候,依然会再执行一次事件。
可是我有时也但愿无头有尾,或者有头无尾,这个咋办?
那咱们设置个 options 做为第三个参数,而后根据传的值判断到底哪一种效果,咱们约定:
leading:false 表示禁用第一次执行
trailing: false 表示禁用中止触发的回调
咱们来改一下代码:
// 第四版
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = new Date().getTime();
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;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}复制代码
在 debounce 的实现中,咱们加了一个 cancel 方法,throttle 咱们也加个 cancel 方法:
// 第五版 非完整代码,完整代码请查看最后的演示代码连接
...
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
...复制代码
咱们要注意 underscore 的实现中有这样一个问题:
那就是 leading:false
和 trailing: false
不能同时设置。
若是同时设置的话,好比当你将鼠标移出的时候,由于 trailing 设置为 false,中止触发的时候不会设置定时器,因此只要再过了设置的时间,再移入的话,就会马上执行,就违反了 leading: false,bug 就出来了,因此,这个 throttle 只有三种用法:
container.onmousemove = throttle(getUserAction, 1000);
container.onmousemove = throttle(getUserAction, 1000, {
leading: false
});
container.onmousemove = throttle(getUserAction, 1000, {
trailing: false
});复制代码
至此咱们已经完整实现了一个 underscore 中的 throttle 函数,恭喜,撒花!
相关的代码能够在 Github 博客仓库 中找到
JavaScript专题系列目录地址:github.com/mqyqingfeng…。
JavaScript专题系列预计写二十篇左右,主要研究平常开发中一些功能点的实现,好比防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特色是研(chao)究(xi) underscore 和 jQuery 的实现方式。
若是有错误或者不严谨的地方,请务必给予指正,十分感谢。若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励。