当前位置: 首页 > Web前端 > JavaScript

手写节流防抖功能

时间:2023-03-27 16:00:11 JavaScript

1.理解防抖和节流功能防抖和节流的概念并不是最早出现在软件工程中的。电子元器件出现防抖,流体流动出现节流。JavaScript是事件驱动的,大量的操作都会触发事件,事件会被加入到事件队列中进行处理。对于一些频繁的事件处理,会造成性能损失。我们可以通过防抖和节流来限制事件的频繁发生。1.1。了解防抖debounce功能场景:在实际开发中,经常会遇到点击按钮请求网络接口的场景。这时候如果用户因为手抖的原因多点了几个按钮,就会在短时间内出现多次对界面的请求。其实这样会造成性能消耗。其实我们只需要监听最后一个按钮,但是我们不知道最后一个会是哪个,所以我们需要做一个延迟触发的操作,比如这个点击。如果300毫秒内没有点击,则视为最后一次。这是防抖功能使用的场景。防抖功能逻辑总结当一个事件被触发时,不会立即触发相应的功能,而是等待一定的时间;事件触发密集时,函数的触发会频繁延迟;只有等待一段时间没有事件触发,才会真正的响应函数1.2知道油门函数场景:我们在开发中会有这样一个需求,要做一些监听逻辑比如鼠标移动时发送网络请求,但是我们知道document.onmousemove监听鼠标移动事件的触发频率很高,我们希望按照一定的频率触发,比如3秒请求一次。中间document.onmousemove无论监听多少次,都只会执行一次。这就是节流功能的使用场景。节流功能逻辑总结。当一个事件被触发时,会执行这个事件的响应函数;中间触发多少次这个事件,执行函数的频率总是固定的;2、实现防抖功能2.1基本实现v-1constdebounceElement=document.getElementById("debounce");consthandleClick=function(e){控制台。log("clickedonce");};//debouncedebounce函数functiondebounce(fn,delay){//设置一个定时器对象,保存最后一个定时器lettimer=null//真正执行的函数function_debounce(){//取消最后一个定时器if(timer){clearTimeout(timer);}//延迟执行timer=setTimeout(()=>{fn()},delay);}return_debounce;}debounceElement.onclick=debounce(handleClick,300);详见前端手写面试题答案2.2this-参数v-2上面的handleClick函数有两个问题,一个是this指向的是window,其实应该是指向debounceElement的,另一个就是不能传递传参。优化:constdebounceElement=document.getElementById("debounce");consthandleClick=function(e){console.log("点击一次",e,this);};functiondebounce(fn,delay){让timer=null;function_debounce(...args){if(timer){clearTimeout(timer);}timer=setTimeout(()=>{fn.apply(this,args)//将this更改为指向传递的参数},delay);}return_debounce;}debounceElement.onclick=debounce(handleClick,300);2.3可选是否立即执行v-3有时候我们想在第一次点击按钮时立即执行,怎么办?优化:constdebounceElement=document.getElementById("debounce");consthandleClick=function(e){console.log("clickedonce",e,this);};//添加一个immediate参数,选择是否立即调用该函数debounce(fn,delay,immediate=false){let定时器=空;让isInvoke=false;//函数是否被调用_debounce(...args){if(timer){clearTimeout(timer);}//如果是第一次调用立即执行if(immediate&&!isInvoke){fn.apply(this.args);isInvoke=真;}埃尔斯e{//如果不是第一次调用延迟执行,则重置isInvoketimer=setTimeout(()=>{fn.apply(this,args);isInvoke=false;},delay);}}return_debounce;}debounceElement.onclick=debounce(handleClick,300,true);2.4取消功能v-4有时候我们设置了一个很长的延迟时间,如果我想在这个时间之前取消点击按钮的事件怎么办?优化:constdebounceElement=document.getElementById("debounce");constcancelElemetnt=document.getElementById("取消");consthandleClick=function(e){console.log("点击一次",e,this);};functiondebounce(fn,delay,immediate=false){lettimer=null;让isInvoke=false;function_debounce(...args){if(timer){clearTimeout(timer);}if(immediate&&!isInvoke){fn.apply(this.args);isInvoke=真;}else{timer=setTimeout(()=>{fn.apply(this,args);isInvoke=false;},delay);}}//在_Debounce中添加取消定时器的取消方法_debounce.cancel=function(){clearTimeout(计时器);定时器=空;};return_debounce;}constdebonceClick=debounce(handleClick,5000,false);debounceElement.onclick=debonceClick;cancelElemetnt.onclick=function(){console.log("事件被取消");debonceClick.cancel();};2.5返回值v-5(最终版)最后一个问题,如果上面的handleClick有返回值,我们应该如何接收呢?优化:使用Promise回调constdebounceElement=document.getElementById("debounce");constcancelElemetnt=document.getElementById("cancel");consthandleClick=function(e){console.log("clickedonce",e,this);return"handleClick返回值";};functiondebounce(fn,delay,immediate=false){lettimer=null;让isInvoke=false;function_debounce(...args){returnnewPromise((resolve,reject)=>{if(timer)clearTimeout(timer);if(immediate&&!isInvoke){try{constresult=fn.apply(this,args);isInvoke=true;resolve(result);//正确的回调}catch(err){拒绝(错误);//错误回调}}else{timer=setTimeout(()=>{try{constresult=fn.apply(this,args);isInvoke=false;resolve(result);//正确的回调}catch(err){reject(err);//错误回调}},delay);}});}_debounce.cancel=function(){clearTimeout(timer);定时器=空;};返回_debounce;}constdebonceClick=debounce(handleClick,300,true);//创建一个debonceCallBack来测试返回值constdebonceCallBack=function(...args){debonceClick.apply(this,args).then((res)=>{console.log({res});});};debounceElement.onclick=debonceCallBack;cancelElemetnt.onclick=()=>{console.log("取消事件");debonceClick.cancel();};3.实现节流功能3.1v-1的基本实现这里是最重要的逻辑,只要监听鼠标移动事件触发的时间减去上次触发的时间大于我们设置的时间间隔,就会进行想要的操作executed就是这样nowTime?lastTime>intervalnowTime:本次监听到鼠标移动事件的时间lastTime:监听到鼠标移动事件的时间interval:我们设置的consthandleMove的时间间隔=()=>{console.log("监听到一个鼠标移动事件");};constthrottle=function(fn,interval){//记录当前事件被触发的时间letnowTime;//记录最后一次触发时间letlastTime=0;//当事件触发时,真正执行的函数function_throttle(){//获取当前触发时间nowTime=newDate().getTime();//当前触发时间减去上次触发时间大于设置的时间间隔if(nowTime-lastTime>interval){fn();最后时间=现在时间;}}return_throttle;};document.onmousemove=throttle(handleMove,1000);3.2this-parameterv-2和防抖一样,上面的代码也会有这个指向问题和传参优化:consthandleMove=(e)=>{console.log("监听到一个鼠标移动事件",e,这个);};constthrottle=function(fn,interval){letnowTime;让lastTime=0;function_throttle(...args){nowTime=newDate().getTime();if(nowTime-lastTime>interval){fn.apply(this,args);最后时间=现在时间;}}return_throttle;};document.onmousemove=throttle(handleMove,1000);3.3optional是否立即执行v-3上面的函数默认第一次是立即触发,如果我们想设置第一次立即触发一次怎么办?优化:consthandleMove=(e)=>{console.log("监听到一个鼠标移动事件",e,this);};constthrottle=function(fn,interval,leading=true){letnowTime;让lastTime=0;function_throttle(...args){nowTime=newDate().getTime();//leading为flase,表示函数不希望立即执行//lastTime为0,表示函数还没有执行if(!leading&&lastTime===0){lastTime=nowTime;}if(nowTime-lastTime>interval){fn.apply(this,args);最后时间=现在时间;}}return_throttle;};document.onmousemove=throttle(handleMove,3000,false);3.4optionalfinal是否执行一次v-4(最终版)如果最后一次监听的移动事件和最后一次执行的时间间隔为小于设定的时间间隔,函数是不会执行的,但是有时候我们希望不管是不是设定的时间间隔都能执行函数,怎么办呢?我们的逻辑是:因为不知道最后一次是哪一次,所以每次都设置一个定时器,定时器的时间间隔就是从下一次执行函数开始的时间;然后每次进来的时候把上次的计时清零这样就可以保证如果这次是最后一次的话,下次执行该函数的时候会执行上一次设置的定时器。consthandleMove=(e)=>{console.log("monitoredamousemovementevent",e,this);};//trailing用于选择是否执行constthrottle=function(fn,interval,leading=true,trailing=false){letnowTime;让lastTime=0;让计时器;function_throttle(...args){nowTime=newDate().getTime();//leading为false表示不想立即执行该函数//lastTime为0表示该函数还没有执行if(!leading&&lastTime===0){lastTime=nowTime;}如果(计时器){clearTimeout(计时器);定时器=空;}if(nowTime-lastTime>=interval){fn.apply(this,args);最后时间=现在时间;返回;}//如果选择了最后一次执行,设置一个定时器if(trailing&&!timer){timer=setTimeout(()=>{fn.apply(this,args);timer=null;lastTime=0;},interval-(nowTime-lastTime));}}return_throttle;};document.onmousemove=throttle(handleMove,3000,true,true);