JS中的防抖、节流

6,960次阅读
没有评论

共计 3048 个字符,预计需要花费 8 分钟才能阅读完成。

一. 概念

防抖:顾名思义,防止抖动,指连续调用时,只有最后一次生效。
节流:节省流量,固定时间间隔触发一次,减小频率。

二. 举例:

  1. 搜索功能,在用户输入结束以后才开始发送搜索请求,可以使用函数防抖来实现;
  2. 验证账号密码功能,在用户输入密码结束以后才开始发送验证请求,可以使用函数防抖来实现;

简单来说:某件事你并不想它太过频繁触发,那么设置一个定时器,每次进来的时候都清除原本的定时器,然后重新开始计时。

  1. 你不想频繁为你女票买单,于是约好每月清空购物车 1 次
  2. 防止过快拖动滚动条,导致 JS 跟不上拖动频率,通过节流限制每秒触发监听的次数(固定时间固定频率)

简单来说:年轻人要保持一日三餐,规律作息,那就通过节流来限制。

三. 手写防抖、节流思路

1. 首先函数需要哪些参数?
  • 都需要传递一个函数进去,返回一个防抖、节流后的新函数,因此第一个参数是需要处理的原函数 fn
  • 光有原函数还不行,因为我们需要规定一个时间间隔。对于防抖而言:时间间隔用于表明最后一次调用时,隔多久没有再次触发,我们才真正调用函数。譬如,放盐时,手一直抖动,直到连续 5 秒没有抖动了,那么才放盐。这个 5 秒就是时间间隔。对于节流而言:时间间隔用于标志,每隔多久我们真正触发函数调用。就像是,一天只吃 3 顿,那么只有每顿间隔 4 个小时,我们才能吃饭。在没到 4 个小时时,不允许吃东西。所以,第二个参数是时间间隔,可写为 wait

现在,我们可以写出防抖和节流函数的基本结构和参数:

// 防抖
function debounce(fn, wait=500) {return function () {}}
 
// 节流
function throttled(fn, wait=500) {return function () {}}
2. 原函数的执行条件是什么?
  • 对于防抖,当连续触发时,我们的目标是,不抖动时才调用。这也就是说,不满足时间间隔时,后一次触发会覆盖掉前一次触发。当满足时间间隔时,才会真正执行。
    满足时间间隔,指的是某一次触发之后,时间间隔范围内,没有下一次触发,这样不产生覆盖,因此会真正执行。由于有时间间隔的判断,因此即便真正执行,也需要延迟到时间间隔之后。这显然是定时器的概念。

  • 对于节流,当连续触发时,我们的目标是,每隔时间间隔时调用一次。这也就是说,我们每次设定固定时间的一个定时器,当定时器存在就什么都不做,当定时器不存在,就设定下一次的定时器。

综上,我们可以大致写出函数的执行时机,结构如下:

// 防抖
 
function debounce(func, wait=500) {
    let timeout;
    return function () {clearTimeout(timeout)
        timeout = setTimeout(() => {// 原函数执行部分}, wait);
    }
}
 
 
// 节流
 
function throttle(fn, wait = 500) {
    let timer = null
    return function () {if (!timer) {timer = setTimeout(() => {// 原函数执行部分}, wait);
        }
    }
}
3. 原函数执行

我们只需要直接调用 fn,即 fn() 即可。但这样不够完善,需要注意两点:

  • 其一是 this 指向,新函数中调用原函数,两者的 this 指向应该一致;
  • 其二是参数,新函数中调用原函数,两者接收的参数应该一致。

故而,我们应该努力让新旧函数在这两方面保持一致。

// 防抖
function debounce(func, wait=500) {
    let timeout;
    return function (...args) {clearTimeout(timeout)
        timeout = setTimeout(() => {fn.apply(this, args)
        }, wait);
    }
}
 
// 节流
function throttle(fn, wait=500) {
    let timer = null
    return function (...args) {if (!timer) {timer = setTimeout(() => {fn.apply(this, args)
            }, wait);
        }
    }
}

注 1:setTimeout 回调中使用 arguments:
若不是箭头函数,接收的是回调的实际参数,而不是新函数的实际参数。
若是箭头函数,由于箭头函数没有 arguments 对象,接收的就是新函数的实际参数。
注 2:setTimeout 回调中使用 this:
若是箭头函数,由于箭头函数没有 this,因此回调中的 this 是外层的 this,
若不是箭头函数,回调中的 this 指向 window。

4. 计时器何时重置?

在上面的代码中,防抖和节流何时进行计时器的重置呢?

  • 对于防抖:每次调用都会清除掉上一次的定时器,因此每次触发都会重置,可以看到 clearTimeout 写在定时器之前。
  • 对于节流:一开始没有定时器,触发事件后检测定时器是否存在,如果不存在则开启定时器执行函数,执行完函数后,在把定时器设为无,准备下一次开启定时器执行函数

故针对节流函数,增加清除操作, 即增加 timer=null 重置语句:

// 防抖
function debounce(func, wait=500) {
    let timeout;
    return function (...args) {clearTimeout(timeout)
        timeout = setTimeout(() => {func.apply(this, args)
        }, wait);
    }
}
 
// 节流
function throttle(fn, wait = 500) {
    let timer = null
    return function (...args) {if (!timer) {timer = setTimeout(() => {fn.apply(this, args);
                timer = null;
            },wait);
        }
    }
}

详情参考

完整的防抖用例


    


完整的节流用例


    

原文地址: JS 中的防抖、节流

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-10-14发表,共计3048字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)