js防抖技术:从原理到实践,如何解决高频事件导致的性能难题

在开发应用时,各位程序开发者们是否遇到一个性能问题,就是当用户网络卡顿时,点击一个按钮短时间内没反应后疯狂点击该按钮,频繁向后端发送请求,或者疯狂输入搜索框导致页面卡顿,频繁调整窗口大小导致重排重绘暴增等常见的性能问题。

一、 防抖的定义

此时聪明的你是否能想到一个解决方法,那就是,我们何不如设置一个定时器,当该事件被触发时,我们并不立即执行这个函数,而是等待一小段时间再触发,且当在等待的时间内没有被触发,便执行一次

想象你在坐电梯的时候,当你进入时,电梯不会立即关闭,而是会等待后面的人全部进入后再关闭(把每一次点击想象成按一次电梯按钮,每一次按按钮都会重置等待时间)

此时你就明白了解决频用户频繁发起请求导致的性能问题的解决方法:防抖

防抖 :在事件被频繁执行时,函数并不立即执行,而是在最后一次触发后等待指定的延迟时间,若延迟期间无新的触发,方才执行一次

二、防抖的实现逻辑

我们先假设在html界面中,我们添加了一个button按钮,且将其id命名为btn,此时我们想要为其添加一个点击事件,使其每点击一次便输出一次hello,我们需要写下如下js代码

1.实现具有打印功能的点击事件

javascript 复制代码
const btn = document.getElementById('btn')
function ptn(){
   console.log('hello')
   }
btn.addEventListenner('click',ptn)

但是你会发现,将你带入疯狂点击该按钮的用户,你就会发现,此时前端会疯狂打印hello,若不能解决这个问题则不能解决相似的前端疯狂向后端发送请求的问题。

2.实现延迟打印(?不成功)

那你此时想到了有一个函数setTimeout(),诶,这个函数好像可以起到一种延迟的效果,或许可以添加请求的间隔?让我们看看

javascript 复制代码
const btn = document.getElementById('btn')
function ptn(){
   console.log('hello')
   }
function debounce(fn,wait){
   return function(){
       setTimeout(function(){
          fn()
       
       },wait)
      }

   }   
   
btn.addEventListenner('click',debounce(ptn,1000))

此时你慢慢地点几次,你或许会发现,诶,我好像实现了这个功能,每过一秒才会打印一次hello,但是但是一旦你连续快速地的点击,你就会惊奇的发现。

啊?为什么在我停下来的时候还会继续打印hello,而且好像打印的间隔不可能有一秒

失败原因

没错,因为单纯使用setTimeout而未使用闭包来保持状态(若想了解闭包,请参考往期文章《js中作用域及闭包的概念与基础应用》导致每次点击都创建了一个打印事件,事件函数频繁触发,间隔时间无法保证,这种被称为防抖失效防抖函数未能实现

3.使用闭包解决未能更新定时器的

我们知道,闭包可以起到一个保存外部函数的变量,延迟销毁的作用

使用闭包处理后的代码
javascript 复制代码
const btn = document.getElementById('btn')

function debounce(ptn,wait){

   var time 
   
   return function(){
   
      clearTimeout(time)
      
      time = setTimeout(function(){
      
         ptn() 
         
      },wait)   
   }
   
   }  
   function ptn(){
   console.log('hello')
   
   }
   
btn.addEventListener('click',debounce(ptn,1000))

经过检验后,你发现,你成功通过闭包实现了''hello''的延迟打印效果,同样的,也可以应用在解决用户大量提交等操作导致的大量计算或布局操作,节省了服务器的算力

扩展

一、添加防抖函数导致this本应的指向改变

上述代码虽然是完成了防抖的基本功能:'频繁触发,只执行最后一次',但是你或许不知道的是,这份代码仍有缺陷,试试仔细观察一下这份代码,添加防抖 再运行后ptn 内的this指向似乎发生了改变

没错,在btn.addEventListener('click',debounce(ptn,1000))的运行时,函数体被btn 调用时,其内部的ptn 函数在被调用时,是独立调用的,而我们知道,当一个函数被独立调用时,其this是指向window全局的。

但是但是,在我们添加这个防抖函数前,我们是这样实现这个点击事件的btn.addEventListenner('click',ptn),此时ptnaddEventListenner触发,出现隐式绑定,this应该是指向btn的。

而在我们添加防抖函数后,却导致ptn函数的this指向了window全局对象,那我们此时就做了一件很糟糕的事情,显然我们需要将它的this指回给btn

方法一

通过提前保存函数内this指向的btn,使用call函数控制其this指回btn

javascript 复制代码
const btn = document.getElementById('btn')

function debounce(ptn,wait){

   var time 
   
   return function(){
      const _this = this
   
      clearTimeout(time)
      
      time = setTimeout(function(){
         
         ptn.call(_this) 
         
      },wait)   
   }
   
   }  
   function ptn(){
   console.log('hello')
   
   }
   
btn.addEventListener('click',debounce(ptn,1000))

方法二

通过利用箭头函数没有this对象,其内部的this是指向btn这一特点,使用call函数控制其this指回btn

javascript 复制代码
const btn = document.getElementById('btn')

function debounce(ptn,wait){

   var time 
   
   return function(){
   
      clearTimeout(time)
      
      time = setTimeout(() => {
         
         ptn.call(this) 
         
      },wait)   
   }
   
   }  
   function ptn(){
   console.log('hello')
   
   }
   
btn.addEventListener('click',debounce(ptn,1000))

二、遗漏addEventListener提供的event事件参数

当我们使用addEventListener触发一个函数时,会默认向其内部传入一个事件函数event对象以及其它对象,用于记录事件发生的详情

而我们遗漏了向防抖函数内部接受这个event对象以及其它的对象 ,以及将其提供给原本被调用的ptn函数,此时我们需要为其接受这个event对象以及其它的对象,以及将其提供给原本被调用的ptn函数。

通过...arg接受及结构向其内部传递

javascript 复制代码
const btn = document.getElementById('btn')

function debounce(ptn,wait){

   var time 
   
   return function(...arg){
   
      clearTimeout(time)
      
      time = setTimeout(() => {
         
         ptn.call(this,...arg) 
         
      },wait)   
   }
   
   }  
   function ptn(){
   console.log('hello')
   
   }
   
btn.addEventListener('click',debounce(ptn,1000))

到此,js防抖技术介绍为止,点赞+关注,后续继续提供实战演示题型

相关推荐
是你的小橘呀1 小时前
从爬楼梯到算斐波那契,我终于弄懂了递归和动态规划这俩 "磨人精"
前端·javascript·面试
m0_740043731 小时前
Vuex中commit和dispatch的核心区别
前端·javascript·html
草字1 小时前
css 按钮的脉冲光环动画,强调动画。
前端·css
BD_Marathon1 小时前
【JavaWeb】CSS_三大选择器
前端·css
jump6801 小时前
柯里化
前端
NeoInfra1 小时前
全面解读ThinkPHP 5.0:现代PHP框架的架构演进与安全实践
前端
一 乐1 小时前
宠物店管理|基于Java+vue的宠物猫店管理管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
BD_Marathon1 小时前
【JavaWeb】JS_数据类型和变量
开发语言·javascript·ecmascript
qq_229058011 小时前
react的3中请求
前端·react.js·前端框架