你们看到都觉得腻的专题--节流防抖实现

本文详细介绍了JavaScript中的节流和防抖两种优化技术,包括概念解释、不同版本的实现方式,并提供了相关代码示例。通过理解这两种技术,可以提升前端应用的性能,尤其在处理高频事件如滚动时。文章最后强调,虽然此类话题常见,但理解其原理和应用场景至关重要。
摘要由CSDN通过智能技术生成

可能80%的前端面试都遇到过这问题,也有无数人写了这方面的文章,但是我还想想写,不止是为了记录下,也是想表达下自己的想法

概念解释

首先说下节流防抖的概念问题吧

  • 节流:说白了就是自定义一个时间间隔,让这个方法每隔这个时间就执行一次,其他触发这个方法的均无视。最常见的像scroll这种操作,没必要执行频率那么高,十分影响性能,搞个几百毫秒执行一次就够意思的了,当然具体执行时间间隔还得视业务需求而定
  • 防抖:说白了就是你还在操作我就不执行它,等你停下来我才执行。还是拿滚动scroll来说事,你监听了文档的scroll,但希望在滚动过程中不执行定义的方法,等停下来才执行,那就是典型的防抖

在开始之前,给大家介绍下setTimeout的返回值,

返回值timeoutID是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时器。需要注意的是setTimeout()setInterval(``)共用一个编号池,技术上,clearTimeout()clearInterval()`` 可以互换。但是,为了避免混淆,不要混用取消定时函数

下面详细来说下

节流

一般来说节流有一下实现方式:

  • 第一种利用闭包,最终返回个函数,先定义一个时间时间标志位previous, 这个值为每次执行完后的时间(首次执行previous为执行时时间),当前时间previous的差值若大于定义的间隔时间则执行,否则直接return掉

看下具体代码

常规版
function throttle(fn, wait=200) {
    const self = this //暂存this,避免在返回函数里丢掉
   let previous = Date.now() //存进来时候的时间
    return function() { //返回新函数,这个函数内部持有对throttle函数作用域的引用,即可取到变量self以及previous的值
           let now = Date.now() //取当前时间
        if(now - previous > wait) { //当当前时间与previous时间差大于自定义间隔值,则执行
            previous = now 
            fn.apply(self, Array.prototype.slice.call(arguments)) //执行,传入参数,注意arguments是伪数组,所以要转化为真数组
        }else{
            return
         }
    }
}

但有这样一个状况,就是在触发情况下,第一次执行发生在等待设定的时间后。如果我想第一次触发就执行,后面的间隔设定的时间再执行,这要怎么做呢?其实也好办,只需要把一开始开始的previous设为0,我每次判断它是否为0,为0的话就立即执行,这不就妥了吗
代码如下:

先行执行版
function throttle(fn, wait=200) {
    const self = this 
   let previous = 0 // 一开始设为0
    return function() { 
          const args = Array.prototype.slice.call(arguments)
          if(!previous) {
            fn.apply(self, args) //previous为0则立即执行
            previous = Date.now() //把当前时间赋给previous,因为把当前时间赋给previous记录的总是上一次的执行时间,一开始为0除外
            return
          }
           let now = Date.now() //取当前时间
        if(now - previous > wait) {
            previous = now 
            fn.apply(self, args)
        }else{
            return
         }
    }
}
  • 第二种是利用setTimeout, 设置个timer存计时器id, 一开始timer为undefined, 以及每次执行前都把timer置为null, 以达到每次触发时判断当前有没有待执行的回调,没有才继续执行

看下代码

利用setTimeout常规版
function throttle(fn, wait=200) {
    const self = this //暂存this,避免在返回函数里丢掉
    let timer //暂存定时器ID
    return function() {
        const args = Array.prototype.slice.call(arguments) //参数
        if(!timer) { //若当前没有待执行的定时器回调
           timer = setTimeout(function() {
                timer = null //这步尤为重要,执行了回调,就把timer置空,证明当前没有待执行的定时器回调了
                fn.apply(self, args) //主函数执行
            }, wait)
        }
    }    
}

同常规版同理,若想第一次触发就执行,只需判断

function throttle(fn, wait=200) {
        ... 
        if(!timer) {
           fn.apply(self, args) //只需要加这句
           timer = setTimeout(function() {
          ...
    }    
}

防抖

如果理解了节流,防抖也是比较容易理解的。简单来说就是如果在归档的时间间隔内执行函数,会重新触发计时。

普通版
function debounce(fun, delay) {
    let timer;
    return function (args) {
        let self = this
        if(timer) { //存在定时器,则清掉
          clearTimeout(timer)
        }
        timer = setTimeout(function () {
            fun.apply(self, Array.prototype.slice.call(arguments))
        }, delay)
    }
}

同样的,以上代码同样存在这样一种情况,就是只有连续触发,停止触发后等待设定的时间后回调才会执行。如果我想第一次触发就执行,后面连续触发都不执行,停止后等待待设定的时间后回调才会执行。

其实我只需要在一开始加个是否存在定时器id便可判断这是否为第一次触发。

先触发版

如下:

function debounce(fun, delay) {
    let timer;
    let self = this
    return function (args) {
        const args = Array.prototype.slice.call(arguments)
        !timer && fun.apply(self, args) //重点是这句,
        if(timer) { //存在定时器,则清掉
          clearTimeout(timer)
        }
        timer = setTimeout(function () {
            fun.apply(self,args)
        }, delay)
    }
}

以上的代码大家都可以copy下面的去验证下,记得测试throttle要换下下面对应的函数名

function con() {
    console.log(222)
}
document.addEventListener('scroll', debounce(con, 200))

总结

可能代码放得有点多,但大家只要记住节流跟防抖的目的,实现的方式有多种,也可以继续扩展,例如配置第一次触发是否立即执行,最后一次触发是否一定执行,也可配置取消节流跟防抖的监听。方法多种多样,把基本的弄懂,其他怎么搞还不是易如反掌,对吧聪明的小伙伴们

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值