前言
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
定义
防抖和节流,都是为了防止短时间内高频繁调用同一接口的优化方案。
防抖
原理:设置延时器,短时间高频率触发只有最后一次触发成功
解释:比如信息流无限加载,滑至底部后,设置一个定时器,间隔1s再发送请求,如果 1s内多次请求,则清除定时器,1s后重新发送请求。也就是说,在1s内无论滑动至底部多少次,最后一次总是会清除上一次的延迟,继而进行自身请求。
无论你如何触底,也只有你停止滑屏后的最后一次会请求成功。
缺点:响应时间太久。
节流
原理:设置请求间隔,短时间高频率触发时,每间隔一定时间进行一次触发。
解释:节流就是设置状态锁,比如每隔1s执行一次,第一次触发后,在下一秒前阻断该期间内的所有请求,保证高频的请求每间隔一段时间进行一次请求,降低了请求频率。
防抖和节流区别
节流不管事件触发有多频繁,都会保证在规定时间内一定会真正执行一次事件处理函数,而函数防抖只是在最后一次触发后才会执行。
实现
防抖
实现原理就是利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。
function debounce(original, wait) {
var timer = null;
return function () {
var context = this, args = arguments;
if (timer) { clearTimeout(timer) };
timer = setTimeout(function () {
original.apply(context, args)
}, wait);
}
}
为了解决防抖长时间才触发的问题,我们可以新增一个immediate参数,在触发函数后立即执行一次
function debounce(original, wait, immediate) {
var timer = null;
return function () {
var context = this, args = arguments;
if (timer) { clearTimeout(timer) };
if (immediate && !timer) {
original.apply(context, args)
};
timer = setTimeout(function () {
original.apply(context, args);
timer = null;
}, wait)
}
}
节流
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
// 时间比较法
function throttle (original, wait) {
var pre = new Date();
return function () {
var now = new Date(), args = arguments;
if(now - pre > wait) {
pre = now;
original.apply(this, args);
}
}
}
// 定时器法
function throttle (original, wait) {
var timer = null;
return function (...args) {
var args = arguments;
if(timer) return;
timer = setTimeout(() => {
original.apply(this, args);
timer = null;
}, wait)
}
}
underscore 源码解析
_.debounce = function(func, wait, immediate) {
// timeout 表示定时器
// result 表示 func 执行返回值
var timeout, result;
// 定时器计时结束后
// 1、清空计时器,使之不影响下次连续事件的触发
// 2、触发执行 func
var later = function(context, args) {
timeout = null;
// if (args) 判断是为了过滤立即触发的
// 关联在于 _.delay 和 restArguments
if (args) result = func.apply(context, args);
};
// 将 debounce 处理结果当作函数返回
var debounced = restArguments(function(args) {
if (timeout) clearTimeout(timeout);
if (immediate) {
// 第一次触发后会设置 timeout,
// 根据 timeout 是否为空可以判断是否是首次触发
var callNow = !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
// 设置定时器
timeout = _.delay(later, wait, this, args);
}
return result;
});
// 新增 手动取消
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
// 根据给定的毫秒 wait 延迟执行函数 func
_.delay = restArguments(function(func, wait, args) {
return setTimeout(function() {
return func.apply(null, args);
}, wait);
});
相比上文的基本版实现,underscore 多了以下几点功能。
- 函数 func 的执行结束后返回结果值 result
- 定时器计时结束后清除 timeout,使之不影响下次连续事件的触发
- 新增了手动取消功能 cancel
- immediate 为 true 后只会在第一次触发时执行,频繁触发回调结束后不会再执行
总结
- 函数防抖和函数节流都是防止某一时间频繁触发,但是这两兄弟之间的原理却不一样。
- 函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。
- 函数节流和防抖都是
闭包、高阶函数的应用