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);// 如果达到了规定的触发时间间隔,触发 handlerif(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)}}
