1. 防抖
主要是用来防止按钮短时间内重复点击。比如在提交表单时,可能会产生单子重复生成的问题。
具体的实现思路就是定义一个定时器,将想要执行的函数放到定时器中,当用户点击时先进行判断,如果存在这个定时器,就清空后重新开始计时。
const debounce = (fun, wait) => {
let timer = 0;
// 返回用户实际调用的防抖函数
return function(...args) {
if (timer) { clearTimeout(timer); }
timer = setTimeout(()=> {
fn.apply(this, args)
}, wait)
}
}
上面的代码逻辑是这样的:传入想要调用的函数和延迟时间,会返回一个真正调用的函数。通过闭包将定时器的标志存在外层函数中。
假如我们点击按钮触发了这个函数,会先判断是否存在 timer
,如果存在,就清除掉,然后重新建一个定时器。等到间歇时间一过,真正想要执行的函数就会执行。
这样的话,当我们在 wait
时间内频繁点击按钮的时候,想要执行的函数会在只会在最后一次点击后 wait
秒之后执行一次。达到了防抖的作用。
但这样也带来一个问题 —— 我第一次点击后,需要等 wait
秒之后才会执行。这肯定是不能接受的,因为我们为了兼容异常操作而影响了正常操作。
所以,我们需要有一个标志符来告诉我们,什么时候是第一次调用,此时应该直接调用传入的函数。否则,需要延时,直到 wait
秒后。
接下来我们看看优化后的版本:
function debounce(fn, wait) {
let timeout;
return function(...arg) {
const context = this;
if(!timeout) fn.apply(this, arg)
clearTimeout(timeout);
timeout = setTimeout(()=> {
timeout = null;
}, wait)
}
}
此时,我们仍然使用 timeout
来判断是否应该调用函数,并且只有当首次或者延时时长过了之后才可以再次调用。
这个函数基本满足了我们的需求,下面是更兼容的版本,是否需要立即调用由用户决定。
function debounce(fn, wait, immediate) {
let timeout;
return function(...arg) {
const context = this;
const later = function() {
timeout = null;
if (!immediate) fn.apply(context, arg);
}
const canNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if(canNow) fn.apply(context, arg);
}
}
我们可以看到,与上一版相比,这一版有以下改动:
- 我们接受第三个参数
immediate
, 用于判断是否需要立即调用。 - 我们通过两个字段
immediate
和timeout
组合起来判断是否需要立即调用。
这样一来,当我们第一次调用时,canNow
为 true
,会直接调用,同时添加一个定时器,指定时间内如果再次点击,便会清除上一个定时器重新定义一个;在指定时间后,调用 later
,将 timeout
设为 null
。
2. 节流
现在想象一下另一个场景,我们列表下拉的时候需要向后台请求数据,这将是一个连续的请求,此时如果使用防抖函数的话请求会在我们不再下拉时才进行,这显然时不合理的。
这个时候我们可以使用节流函数。与防抖不同,节流会让函数在固定时间内仅仅执行一次,下次调用时会根据函数是否在执行而选择是否再执行下一次。
函数节流的要点是,声明一个变量当标志位,记录当前代码是否在执行。如果空闲,则可以正常触发方法执行。如果代码正在执行,则取消这次方法执行,直接return。
function throttle(func, wait, mustRun) {
var timeout,
startTime = new Date();
return function() {
var context = this,
args = arguments,
curTime = new Date();
clearTimeout(timeout);
// 如果达到了规定的触发时间间隔,触发 handler
if(curTime - startTime >= mustRun){
func.apply(context,args);
startTime = curTime;
// 没达到触发间隔,重新设定定时器
}else{
timeout = setTimeout(func, wait);
}
};
};
或者:
function throttol(fn, wait) {
let canRun = true;
return function(){
if (!canRun) { return }
canRun = false;
setTimeout((...arg) => {
fn.apply(this,arg);
canRun = true;
}, wait)
}
}