深入理解JS---图解节流

282 阅读4分钟

节流定义

当持续触发事件时,在规定时间段内只能调用一次回调函数。如果在规定时间内触发了该事件,则什么也不做。

(类似于发技能,发出一次技能后,要等待技能冷却时间结束,在此期间的点击都会被忽略,当技能冷却时间结束,点击即可触发技能; 触发技能后,接着等待技能冷却时间结束,在此期间的点击都会被忽略,当技能冷却时间结束;点击即可触发技能;...)

两种实现方法

定时器版

先设置一个局部变量timer,初始化为null,第一次触发函数后,设置定时器的同时赋值给timer, 定时器任务完成期间的调用会被忽略不执行任何操作,直至定时器完成将变量timer置为null;再次调用时,再次定时器,设置定时器的同时赋值给timer, 定时器任务完成期间的调用会被忽略不执行任何操作,直至定时器完成将变量timer置为null;...。

 function throttle (fn, wait) {
   let timer = null;
   return function (...args) {
     if (!timer) {
       timer = setTimeout(() => {
         fn.apply(this, args)
         timer = null;
       }, wait)
     }
   }
 }

特点:第一次触发事件时,不会立即执行函数,到了规定时间后才会执行。 之后连续频繁地触发事件,也是到了规定时间才会执行一次(因为定时器)。在等待经过时间间隔的期间,第2次、第3次的调用会被忽略,当等待过规定时间后,将执行第1次触发放入的回调;接着,第4次触发会被放入定时器的回调函数中,在等待经过时间间隔的期间,第5次的调用会被忽略,等待过规定时间后,将执行第4次触发放入的回调;接着,最后一次触发也就是第6次触发会被放入定时器的回调函数中,等待过规定时间后,将执行第6次触发放入的回调。

当最后一次停止触发后,由于定时器的延时,还会执行一次回调函数(那也是上一次成功成功触发执行的回调,而不是你最后一次触发产生的)。一句话总结就是延时回调,你能看到的回调都是上次成功触发产生的,而不是你此刻触发产生的。

定时器节流.png

时间戳版

先设置一个last记录上一次触发的时间戳,在返回的函数内部设置一个变量now记录此刻调用的时间,判断now-last大于设置时间则执行函数,并将last设置为now。否则什么也不做。

 function throttleTwo (fn, wait) {
   let last = 0;
   return function (...args) {
     let now = Date.now();
     let context = this;
     if (now-last >= wait) {
       fn.apply(context, args)
       last = now;
     }
   }
 }

特点: 第一次会立即执行,之后连续频繁地触发事件,也是超过了规定时间才会执行下一次。最后一次触发事件,也不会执行(说明:如果你最后一次触发时间大于规定时间,这样就算不上连续频繁触发了).

第一次触发函数会立即执行,之后等待经过时间间隔的期间,第2次、第3次的调用会被忽略,当等待过规定时间后,将第4次触发函数立即执行;之后等待经过时间间隔的期间,第5次、第6次的调用会被忽略,当等待过规定时间后,将第7次触发函数立即执行;由于第8次调用在时间间隔内,所以不会执行。

时间戳节流.png

这两者最大的区别:是时间戳版的函数触发是在规定时间开始的时候,而定时器版的函数触发是在规定时间结束的时候。

进阶实现

时间戳+定时器版:

总体上还是时间戳的思路,用定时器去执行最后一次的触发。

 function throttleThree (fn, wait) {
   let last = 0;
   let timer = null;
   return function (...args) {
     let context = this;
     let now = Date.now();
     clearTimeout(timer)
     if (now - last > wait) {
       fn.apply(context, args)
       last = now;
     } else {
       timer = setTimeout(() => {
         fn.apply(context, args)
       }, wait)
     }
   }
 }

总结

函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。

应用场景

两个条件: 1,客户连续频繁地触发事件(就是事件触发的时间间隔至少是要比规定的时间要短) 2,客户不再只关心"最后一次"操作后的结果反馈.而是在操作过程中持续的反馈.

  • 鼠标不断点击触发,点击事件在规定时间内只触发一次(单位时间内只触发一次)
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
  • scroll,resize, touchmove, mousemove等极易持续性促发事件的相关动画问题,降低频率
  • DOM 元素的拖拽功能实现(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 计算鼠标移动的距离(mousemove)
  • Canvas 模拟画板功能(mousemove)
  • 搜索联想(keyup)

参考

链接:juejin.cn/post/684490…