✅✅包教包会——手写防抖节流


theme: cyanosis

前言

记得当初看这篇大佬的文章一杯茶的时间🍵,带你彻底学会手写防抖节流 – 掘金 (juejin.cn)已经对防抖节流有了个清晰的认识,但那个时候由于真的是第一次接触到防抖节流,对它的手写方式还是很迷,卡在着卡了很久。

所以今天打算在那篇文章的基础上做一些补充,让小白对防抖节流的手写能够真正掌握。

防抖节流的概念

  • 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
  • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

一个经典的比喻:

想象每天上班大厦底下的电梯。把电梯完成一次运送,类比为一次函数的执行和响应

假设电梯有两种运行策略 debounce 和 throttle,超时设定为15秒,不考虑容量限制

电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖

电梯第一个人进来后,15秒后准时运送一次,这是节流

再举个不太严谨的比喻:

防抖:就是王者荣耀里的回城,一段时间内被打断,就又要重新计时。

节流:就是王者荣耀里英雄的技能,像东皇的大一般都是20s准时来一次。

手写

防抖

  • 首先我们先简单的模拟一个按钮被点击的过程。

let addBtn=document.getElementById('add')
function addOne(){
  console.log('增加一个')
}
addBtn.addEventListener('click',addOne)
  • 加入防抖功能
// debounce.js
let addBtn=document.getElementById('add')

function addOne(){
  console.log('增加一个')
}

function debounce(fun,time){
    setTimeout(()=>{
      fun()
    },time)
}
addBtn.addEventListener('click',debounce(addOne,2000))
  • 现在延时的目的是达到了但是每次点击都会新增一个新的setTimeout而且并不能达到我们多次点击只执行一次的效果。

  • 这时候就需要clearTimeout登场了,我们需要在我们点击了按钮后也就是debounce执行时要先把之前的setTimeout先清除再重新计时。

这时我们会很自然地想到这个写法:

function debounce(fun, time) {
    let timer;
    
     // 如果之前就存在定时器,就要把之前那个定时器删除
        if (timer) {
            clearTimeout(timer)
        }

        timer = setTimeout(() => {
            fun()
        }, time)
    
}

但是这样写的话拿的到之前定时器的值吗?🤔

当然是拿不到的,因为每次都会重新let timer,所以这个时候我们就要使用闭包(延迟了变量的生命周期)了。

如果你还不太理解闭包,可以参考这篇文章🐟 🐟包教包会——作用域链+闭包 – 掘金 (juejin.cn)

  • 引入闭包后
function addOne(){
  console.log('增加一个')
}

function debounce(fun, time) {
    let timer;
    
    return function(){
     // 如果之前就存在定时器,就要把之前那个定时器删除
        if (timer) {
            clearTimeout(timer)
        }

        timer = setTimeout(() => {
            fun()
        }, time)
        
    }
}

addBtn.addEventListener('click',debounce(addOne,2000))

这里还有一点需要知道的,每执行一次addEventListener,debounce因为返回的是一个函数,并结合addEventListener的特点,会直接执行debounce返回的函数,不会出现每次都let timer

  • 现在我们的一个防抖功能就完成了,但是这还没完,如果我们在addOne()打印this会发现我们这样执行的this是指向Window的。

  • 这当然不是我们所希望的,我们需要使用apply来改变this指向,再者就是我们需要考虑到执行函数的参数,因为不同的函数肯定会有不同的参数传入,对于参数我们可以使用arguments处理。

function debounce(fun, time) {
    let timer;
    
    return function(){
     // 如果之前就存在定时器,就要把之前那个定时器删除
        if (timer) {
            clearTimeout(timer)
        }

        timer = setTimeout(() => {
            fun.apply(this, arguments)
        }, time)
        
    }
}
  • 这样一个防抖就写出来了

节流

  • 首先我们先模拟一个触发事件。
  • 接下来我们封装一个节流函数,跟防抖一样我们也需要利用闭包,顺便再加一个参数接收节流时间。
// throttle.js
function scrollTest(){ 
    console.log('现在我触发了')
}

function throttle(fun,time){
  return function(){
    fun()
  }
}
document.addEventListener('scroll',throttle(scrollTest,3000))

这里用闭包是为了后面获得上一次的时间

  • 因为我们的节流是在一段时间内执行一次也就是说如果两次鼠标滚动的时间间隔未到所设置的时间则不执行
  • 那我们可以记录一下每次滚动的时间戳来进行对比。
  • 当然我们也需要像防抖一样改变this指向和接收参数,最后完成后是这样的。
// throttle.js
...
function throttle(fun,time){
  let t1=0 //初始时间
  return function(){
    let t2=new Date() //当前时间
    if(t2-t1>time){
      fun.apply(this,arguments)
      t1=t2
    }
  }
}

应用场景

防抖在连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

节流在间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 搜索框,搜索联想功能

参考文章

一杯茶的时间🍵,带你彻底学会手写防抖节流 – 掘金 (juejin.cn)

本文正在参加「金石计划 . 瓜分6万现金大奖」

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容