throttle(节流)和debounce(防抖)

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

防抖和节流是针对响应跟不上触发频率这类问题的两种解决方案。 在给DOM绑定事件时,有些事件我们是无法控制触发频率的。 如鼠标移动事件onmousemove, 滚动滚动条事件onscroll,窗口大小改变事件onresize,瞬间的操作都会导致这些事件会被高频触发。 如果事件的回调函数较为复杂,就会导致响应跟不上触发,出现页面卡顿,假死现象。 在实时检查输入时,如果我们绑定onkeyup事件发请求去服务端检查,用户输入过程中,事件的触发频率也会很高,会导致大量的请求发出,响应速度会大大跟不上触发。

debounce,去抖动。策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。 这是debounce的基本思想,在后期又扩展了前缘debounce,即执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。

延迟debounce,示意图(动作在有效周期内后置)

debounce - 图1

前缘debounce,示意图(动作前置)

debounce - 图2
debounce的特点是当事件快速连续不断触发时,动作只会执行一次。 延迟debounce,是在周期结束时执行,前缘debounce,是在周期开始时执行。但当触发有间断,且间断大于我们设定的时间间隔时,动作就会有多次执行。

debounce 的实现:

版本1: 周期内有新事件触发,清除旧定时器,重置新定时器;这种方法,需要高频的创建定时器。

暴力版: 定时器期间,有新操作时,清空旧定时器,重设新定时器

  1. var debounce = (fn, wait) => {
  2. let timer, timeStamp=0;
  3. let context, args;
  4. let run = ()=>{
  5. timer= setTimeout(()=>{
  6. fn.apply(context,args);
  7. }, wait);
  8. }
  9. let clean = () => {
  10. clearTimeout(timer);
  11. }
  12. return function(){
  13. context=this;
  14. args=arguments;
  15. let now = (new Date()).getTime();
  16. if (now-timeStamp < wait) {
  17. console.log('reset',now);
  18. clean(); // clear running timer
  19. run(); // reset new timer from current time
  20. } else {
  21. console.log('set',now);
  22. run(); // last timer alreay executed, set a new timer
  23. }
  24. timeStamp=now;
  25. }
  26. }

版本2: 周期内有新事件触发时,重置定时器开始时间撮,定时器执行时,判断开始时间戳,若开始时间戳被推后,重新设定延时定时器。

// 优化版: 定时器执行时,判断start time 是否向后推迟了,若是,设置延迟定时器

  1. var debounce = (fn, wait) => {
  2. let timer, startTimeStamp=0;
  3. let context, args;
  4. let run = (timerInterval)=>{
  5. timer= setTimeout(()=>{
  6. let now = (new Date()).getTime();
  7. let interval = now - startTimeStamp
  8. if(interval < timerInterval){
  9. // the timer start time has been reset, so the interval is less than timerInterval
  10. console.log('debounce reset',timerInterval-interval);
  11. startTimeStamp=now;
  12. run(wait-interval); // reset timer for left time
  13. } else {
  14. fn.apply(context,args);
  15. clearTimeout(timer);
  16. timer=null;
  17. }
  18. }, timerInterval);
  19. }
  20. return function(){
  21. context=this;
  22. args=arguments;
  23. let now = (new Date()).getTime();
  24. startTimeStamp=now;
  25. if(!timer){
  26. console.log('debounce set',wait);
  27. run(wait); // last timer alreay executed, set a new timer
  28. }
  29. }
  30. }

版本3: 在版本2基础上增加是否立即执行选项:

// 增加前缘触发功能

  1. var debounce = (fn, wait, immediate=false) => {
  2. let timer, startTimeStamp=0;
  3. let context, args;
  4. let run = (timerInterval)=>{
  5. timer= setTimeout(()=>{
  6. let now = (new Date()).getTime();
  7. let interval=now-startTimeStamp
  8. if(interval<timerInterval){ // the timer start time has been reset,so the interval is less than timerInterval
  9. console.log('debounce reset',timerInterval-interval);
  10. startTimeStamp=now;
  11. run(wait-interval); // reset timer for left time
  12. }else{
  13. if(!immediate){
  14. fn.apply(context,args);
  15. }
  16. clearTimeout(timer);
  17. timer=null;
  18. }
  19. },timerInterval);
  20. }
  21. return function(){
  22. context=this;
  23. args=arguments;
  24. let now = (new Date()).getTime();
  25. startTimeStamp=now; // set timer start time
  26. if(!timer){
  27. console.log('debounce set',wait);
  28. if(immediate) {
  29. fn.apply(context,args);
  30. }
  31. run(wait); // last timer alreay executed, set a new timer
  32. }
  33. }
  34. }

https://blog.csdn.net/hupian1989/article/details/80920324