1. 防抖

主要是用来防止按钮短时间内重复点击。比如在提交表单时,可能会产生单子重复生成的问题。

具体的实现思路就是定义一个定时器,将想要执行的函数放到定时器中,当用户点击时先进行判断,如果存在这个定时器,就清空后重新开始计时。

  1. const debounce = (fun, wait) => {
  2. let timer = 0;
  3. // 返回用户实际调用的防抖函数
  4. return function(...args) {
  5. if (timer) { clearTimeout(timer); }
  6. timer = setTimeout(()=> {
  7. fn.apply(this, args)
  8. }, wait)
  9. }
  10. }

上面的代码逻辑是这样的:传入想要调用的函数和延迟时间,会返回一个真正调用的函数。通过闭包将定时器的标志存在外层函数中。

假如我们点击按钮触发了这个函数,会先判断是否存在 timer,如果存在,就清除掉,然后重新建一个定时器。等到间歇时间一过,真正想要执行的函数就会执行。

这样的话,当我们在 wait 时间内频繁点击按钮的时候,想要执行的函数会在只会在最后一次点击后 wait 秒之后执行一次。达到了防抖的作用。

但这样也带来一个问题 —— 我第一次点击后,需要等 wait 秒之后才会执行。这肯定是不能接受的,因为我们为了兼容异常操作而影响了正常操作。

所以,我们需要有一个标志符来告诉我们,什么时候是第一次调用,此时应该直接调用传入的函数。否则,需要延时,直到 wait 秒后。

接下来我们看看优化后的版本:

  1. function debounce(fn, wait) {
  2. let timeout;
  3. return function(...arg) {
  4. const context = this;
  5. if(!timeout) fn.apply(this, arg)
  6. clearTimeout(timeout);
  7. timeout = setTimeout(()=> {
  8. timeout = null;
  9. }, wait)
  10. }
  11. }

此时,我们仍然使用 timeout 来判断是否应该调用函数,并且只有当首次或者延时时长过了之后才可以再次调用。

这个函数基本满足了我们的需求,下面是更兼容的版本,是否需要立即调用由用户决定。

  1. function debounce(fn, wait, immediate) {
  2. let timeout;
  3. return function(...arg) {
  4. const context = this;
  5. const later = function() {
  6. timeout = null;
  7. if (!immediate) fn.apply(context, arg);
  8. }
  9. const canNow = immediate && !timeout;
  10. clearTimeout(timeout);
  11. timeout = setTimeout(later, wait);
  12. if(canNow) fn.apply(context, arg);
  13. }
  14. }

我们可以看到,与上一版相比,这一版有以下改动:

  1. 我们接受第三个参数 immediate, 用于判断是否需要立即调用。
  2. 我们通过两个字段 immediatetimeout 组合起来判断是否需要立即调用。

这样一来,当我们第一次调用时,canNowtrue,会直接调用,同时添加一个定时器,指定时间内如果再次点击,便会清除上一个定时器重新定义一个;在指定时间后,调用 later,将 timeout 设为 null

2. 节流

现在想象一下另一个场景,我们列表下拉的时候需要向后台请求数据,这将是一个连续的请求,此时如果使用防抖函数的话请求会在我们不再下拉时才进行,这显然时不合理的。

这个时候我们可以使用节流函数。与防抖不同,节流会让函数在固定时间内仅仅执行一次,下次调用时会根据函数是否在执行而选择是否再执行下一次。

函数节流的要点是,声明一个变量当标志位,记录当前代码是否在执行。如果空闲,则可以正常触发方法执行。如果代码正在执行,则取消这次方法执行,直接return。

  1. function throttle(func, wait, mustRun) {
  2. var timeout,
  3. startTime = new Date();
  4. return function() {
  5. var context = this,
  6. args = arguments,
  7. curTime = new Date();
  8. clearTimeout(timeout);
  9. // 如果达到了规定的触发时间间隔,触发 handler
  10. if(curTime - startTime >= mustRun){
  11. func.apply(context,args);
  12. startTime = curTime;
  13. // 没达到触发间隔,重新设定定时器
  14. }else{
  15. timeout = setTimeout(func, wait);
  16. }
  17. };
  18. };

或者:

  1. function throttol(fn, wait) {
  2. let canRun = true;
  3. return function(){
  4. if (!canRun) { return }
  5. canRun = false;
  6. setTimeout((...arg) => {
  7. fn.apply(this,arg);
  8. canRun = true;
  9. }, wait)
  10. }
  11. }