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

性能——防抖和节流

时间:2023-04-02 22:06:15 HTML

防抖和节流1.在指定时间内去抖动(debounce),该方法只能执行一次,直接忽略多余的事件(下划线,lodash库的debounce都是这样做的).而这个时间的计算是从上次触发监控事件开始算起的。例如:当你按下按钮关电梯门时,电梯不会关里面的门,而是等着看是否有人要上电梯(也就是要延迟操作);resize事件(如果只是看resize后的最终效果,可以使用debounce);提交按钮防抖操作(一般操作需要立即进行)。1.2基于概念的实现:实现debounce的关键点是如何计算延迟时间(实现throttle的关键点同上)。函数去抖动(cb,延迟){vartimeoutId;returnfunctiondebounced(){//每次调用都会清除上次的延迟,然后重新创建延迟timeoutId&&clearTimeout(timeoutId);timeoutId=setTimeout(()=>{cb.apply(this,arguments);},delay)}}1.3各种实现中添加的一些附加功能:前沿(或“立即”)是否执行操作然后等待,或等待,然后执行操作。Leadingedgedebounce:Delayeddebounce:延迟时间的计算方式是一样的,唯一不同的是函数被触发调用的时间点。计算延迟期间的组件卸载等取消操作会导致清理操作。具有“取消操作”和“前沿”的防抖方法(不是一次性写入,多次优化结果):functiondebounce(cb,delay,immediate){vartimeoutId;//cancel操作,只依赖于timeoutId,因此cancel函数要避免在debounced函数中声明(这样会导致每次调用debounced都要重新声明cancel的定义)varcancel=debounced.cancel=functioncancel(){if(timeoutId){clearTimeout(timeoutId);timeoutId=null;}}functiondebounced(){varargs=arguments;变种自我=这个;功能动作(){cb.apply(自我,参数);}//使用timeoutId标记cb是否被调用if(immediate&&!timeoutId){action();}//清除之前的延迟cancel();/**开启新的延迟*immediate=true表示延迟取消操作,否则执行延迟函数。*/timeoutId=setTimeout(immediate?cancel:action,delay)}returndebounced;}上面的实现有问题。它使用setTimeout来创建/取消延迟,而不是动态计算延迟。这会导致连续操作中有耗时操作时,setTimeout回调没有及时触发。函数睡眠(延迟){varpre=Date.now();while(Date.now()-pre{console.log(`a+b=${a+b}`)returna+b;},100,true)//Case1debounced(1,23)sleep(delay)//debounced同步方法,导致`setTimeout(immediate?cancel:action,delay)`回调函数不会执行debounced(1,24)//虽然已经过了600ms,但还是忽略了。//Case2debounced(1,25)setTimeout(()=>{debounced(1,26)//改成异步,可以触发},600)不过这个实现比较简单明了,大部分情况不会有问题。Underscoredebounce也使用了类似的方法。2.Throttle(节流阀)在指定时间(执行时间窗口)内,该方法只能执行一次,多余的事件直接忽略(下划线,lodash库的throttle都是这样做的)。而这个时间的计算是从最后一次执行该方法开始算起的。例如:向上滚动显示更多商品时调用接口的频率会降低,一般处理scroll、resize、touchmove、mousemove等事件处理函数。2.1基于概念的实现functionthrottle(func,delay){vartimeoutId;varcancel=throttled.cancel=functioncancel(){if(timeoutId){clearTimeout(timeoutId);timeoutId=null;}}functionthrottled(){varcontext=this;var参数=参数;if(!timeoutId){timeoutId=setTimeout(function(){cancel();action();},delay)}functionaction(){func.apply(context,args);}}returnthrottled;}逻辑很简单,但是func只能延迟执行;严格来说,时间计算规则是错误的,这个实现的时间计算是从第一个throttled函数调用开始计算的,正确的应该是从最后一个func调用开始计算。2.2改进:并参考增加前沿(称为leadinginthrottle,trailingcall)来控制leading方法调用,延迟时间必须是动态的,因为在一段时间内,throttled函数调用的延迟时间以后一定要短。functionthrottle(func,delay,leading){vartimeoutId,preCallTime;varcancel=throttled.cancel=functioncancel(){if(timeoutId){clearTimeout(timeoutId);timeoutId=null;}}functionthrottled(){varcontext=this;var参数=参数;varnow=Date.now();//头调用if(leading){if(!preCallTime){preCallTime=0;}varremain=delay-(now-preCallTime);//使用剩余时间来控制动作是否可以调用(防止触发多个动作)if(remain<=0){action();}preCallTime=现在;返回;}if(!timeoutId){timeoutId=setTimeout(function(){cancel();action();},delay)}functionaction(){func.apply(context,args);}}返回节流;}铅ing方法通过计算剩余时间来控制func的执行,不再依赖setTimeout;trailing方法中的回调函数必须延迟,不能避免依赖setTimeout;trailing方法代码逻辑的延迟本质上就是剩余时间,代码组织也可以优化2.3改进函数throttle(func,delay,leading){vartimeoutId,preCallTime;varcancel=throttled.cancel=functioncancel(){if(timeoutId){clearTimeout(timeoutId);timeoutId=null;}}functionthrottled(){varcontext=this;var参数=参数;varnow=Date.now();如果(!preCallTime){preCallTime=领先?0:现在;}varremain=delay-(now-preCallTime);如果(保持<=0){动作();//未到指定时间时第一次调用&尾随模式}elseif(!leading&&!timeoutId){timeoutId=setTimeout(action,remain);}functionaction(){func.apply(context,args);呼叫前时间=现在;取消();}}returnthrottled;}总结从上面的描述我们可以了解到,防抖和节流都是控制“该方法在指定时间内只能执行一次”,它们的区别在于如何计算时间间隔:debounce从最后一次调用debounced函数开始计算(新触发器将覆盖最后一个触发器);防抖油门是之前的第一次调用throttled方法开始计时(新出发的时间-最后一次触发的时间)节流,统一控制频率?lodash确实统一了,但是代码可读性很差。整理自gitHub笔记