节流和防抖在开发项目过程当中很常见,例如 input 输入实时搜索、scrollview 滚动更新了,等等,大量的场景须要咱们对其进行处理。咱们由 Lodash 来介绍,直接进入主题吧。前端
防抖 (debounce) :屡次触发,只在最后一次触发时,执行目标函数。git
lodash.debounce(func, [wait=0], [options={}])
复制代码
节流(throttle):限制目标函数调用的频率,好比:1s内不能调用2次。github
lodash.throttle(func, [wait=0], [options={}])
复制代码
lodash 在 opitons 参数中定义了一些选项,主要是如下三个:缓存
根据 leading 和 trailing 的组合,能够实现不一样的调用效果:app
{leading: true, trailing: false}:只在延时开始时调用函数
{leading: false, trailing: true}:默认状况,即在延时结束后才会调用函数ui
{leading: true, trailing: true}:在延时开始时就调用,延时结束后也会调用this
deboucne 还有 cancel 方法,用于取消防抖动调用spa
防抖 (debounce):code
addEntity = () => {
console.log('--------------addEntity---------------')
this.debounceFun();
}
debounceFun = lodash.debounce(function(e){
console.log('--------------debounceFun---------------');
}, 500,{
leading: true,
trailing: false,
})
复制代码
首次点击时执行,连续点击且时间间隔在500ms以内,再也不执行,间隔在500ms以外再次点击,执行。
节流(throttle):
addEntity = () => {
console.log('--------------addEntity---------------');
this.throttleFun();
}
throttleFun = lodash.throttle(function(e){
console.log('--------------throttleFun---------------');
}, 500,{
leading: true,
trailing: false,
})
复制代码
首次点击时执行,连续点击且间隔在500ms以内,500ms以后自动执行一次(注:连续点击次数时间以后小于500ms,则不会自动执行),间隔在500ms以外再次点击,执行。
// 这个是用来获取当前时间戳的
function now() {
return +new Date()
}
/** * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {boolean} immediate 设置为ture时,是否当即调用函数 * @return {function} 返回客户调用函数 */
function debounce (func, wait = 50, immediate = true) {
let timer, context, args
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空缓存的定时器序号
timer = null
// 延迟执行的状况下,函数会在延迟函数中执行
// 使用到以前缓存的参数和上下文
if (!immediate) {
func.apply(context, args)
context = args = null
}
}, wait)
// 这里返回的函数是每次实际调用的函数
return function(...params) {
// 若是没有建立延迟执行函数(later),就建立一个
if (!timer) {
timer = later()
// 若是是当即执行,调用函数
// 不然缓存参数和调用上下文
if (immediate) {
func.apply(this, params)
} else {
context = this
args = params
}
// 若是已有延迟执行函数(later),调用的时候清除原来的并从新设定一个
// 这样作延迟函数会从新计时
} else {
clearTimeout(timer)
timer = later()
}
}
}
复制代码
/** * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {object} options 若是想忽略开始函数的的调用,传入{leading: false}。 * 若是想忽略结尾函数的调用,传入{trailing: false} * 二者不能共存,不然函数不能执行 * @return {function} 返回客户调用函数 */
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 以前的时间戳
var previous = 0;
// 若是 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function() {
// 若是设置了 leading,就将 previous 设为 0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空一是为了防止内存泄漏,二是为了下面的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 得到当前时间戳
var now = _.now();
// 首次进入前者确定为 true
// 若是须要第一次不执行函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 若是当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 若是设置了 trailing,只会进入这个条件
// 若是没有设置 leading,那么第一次会进入这个条件
// 还有一点,你可能会以为开启了定时器那么应该不会进入这个 if 条件了
// 其实仍是会进入的,由于定时器的延时
// 并非准确的时间,极可能你设置了2秒
// 可是他须要2.2秒才触发,这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 若是存在定时器就清理掉不然会调用二次回调
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启一个定时器
// 而且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
复制代码
想看更过系列文章,点击前往 github 博客主页
走在最后,欢迎关注:前端瓶子君,每日更新