debounce vs throttle
2020-08-20 本文已影响0人
vavid
常说的函数防抖和函数节流,都是为了不频繁触发某类操作或者接口请求。
debounce(防反跳): 防抖
throttle(节流阀): 节流
debounce
举个例子:当从键盘输入内容时,监听keyUp事件,当连续不断的输入时,我们使用debounce
,它的想法是这样的:可以搜了吗?哦,你还在输啊,那我再等等吧~
所以debounce的一个应用场景:搜索词联想匹配
代码实现
/**
* 函数防抖:返回函数连续调用时,间隔时间必须大于或等于 wait,func 才会执行
* @param {function} func 传入函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,调用触发于开始边界而不是结束边界
* @return {function} 返回客户调用函数
*/
debounce: function (func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function () {
// 据上一次触发时间间隔
var last = new Date().getTime() - timestamp;
// 上次被包装函数被调用时间间隔last小于设定时间间隔wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function () {
context = this;
args = arguments;
timestamp = new Date().getTime();
var callNow = immediate && !timeout;
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
}
这样调用:
function handleSomething(e){
// ... do something
}
XX.debounce(handleSomething, 150, false)
throttle
举个例子:当页面上下滚动时,要根据页面高度,页面楼层导航需要吸顶或者取消吸顶时,我们使用throttle
,它的想法是这样的:你别让我一直判断要不要吸顶!我很累的!我隔XXms给你判断一下吧!
所以debounce的一个应用场景:页面吸顶导航或悬浮导航
代码实现
throttle = function(func, wait, options) {
var context, args, result;
// setTimeout 的 handler
var timeout = null;
// 标记时间戳
// 上一次执行回调的时间戳
var previous = 0;
// 如果没有传入 options 参数
// 则将 options 参数置为空对象
if (!options)
options = {};
var later = function() {
// 如果 options.leading === false
// 则每次触发回调后将 previous 置为 0
// 否则置为当前时间戳
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
result = func.apply(context, args);
// 这里的 timeout 变量一定是 null 了吧
// 是否没有必要进行判断?
if (!timeout)
context = args = null;
};
// 以滚轮事件为例(scroll)
// 每次触发滚轮事件即执行这个返回的方法
// _.throttle 方法返回的函数
return function() {
// 记录当前时间戳
var now = new Date().getTime();
// 第一次执行回调(此时 previous 为 0,之后 previous 值为上一次时间戳)
// 并且如果程序设定第一个回调不是立即执行的(options.leading === false)
// 则将 previous 值(表示上次执行的时间戳)设为 now 的时间戳(第一次触发时)
// 表示刚执行过,这次就不用执行了
if (!previous && options.leading === false)
previous = now;
// 距离下次触发 func 还需要等待的时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 要么是到了间隔时间了,随即触发方法(remaining <= 0)
// 要么是没有传入 {leading: false},且第一次触发回调,即立即触发
// 此时 previous 为 0,wait - (now - previous) 也满足 <= 0
// 之后便会把 previous 值迅速置为 now
// ========= //
// remaining > wait,表示客户端系统时间被调整过
// 则马上执行 func 函数
// @see https://blog.coding.net/blog/the-difference-between-throttle-and-debounce-in-underscorejs
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
// 解除引用,防止内存泄露
timeout = null;
}
// 重置前一次触发的时间戳
previous = now;
// 触发方法
// result 为该方法返回值
result = func.apply(context, args);
// 引用置为空,防止内存泄露
// 感觉这里的 timeout 肯定是 null 啊?这个 if 判断没必要吧?
if (!timeout)
context = args = null;
} else if (!timeout && options.trailing !== false) { // 最后一次需要触发的情况
// 如果已经存在一个定时器,则不会进入该 if 分支
// 如果 {trailing: false},即最后一次不需要触发了,也不会进入这个分支
// 间隔 remaining milliseconds 后触发 later 方法
timeout = setTimeout(later, remaining);
}
// 回调返回值
return result;
};
};
这样调用:
function handleSomething(e){
// ... do something
}
XX. throttle(handleSomething, 150, false)