找回密码
 立即注册
首页 资源区 代码 JavaScript 防抖和节流

JavaScript 防抖和节流

娥搽裙 2025-6-13 19:38:39
概念

防抖(Debounce)是指在事件触发后,只有在经过设定的时间间隔内没有再次触发,事件处理器才会执行。防抖限制事件的触发时间间隔,避免事件被频繁触发。这适用于在搜索框输入建议、调整窗口大小、表单验证、表单提交等场景。
节流(Throttle)则是指在一定时间间隔内,不论事件触发多少次,事件处理器只执行一次。节流控制执行时间间隔,降低事件的执行频率。这适用于页面滚动加载、鼠标移动、游戏角色控制、实时数据图表更新等场景。
注意:事件触发≠函数执行。触发在执行前。
比如,我反馈了一个Bug,但是该Bug不一定在被修复。
疑问:防抖和节流都可用于控制事件处理函数的执行频率,那它们能否互相替代?
防抖是为了确保多次触发、一次执行,只关注最终状态;节流是为了确保函数的周期性执行,更关注过程中的均匀响应。
故防抖、节流不能完全互相替代,也就是说它两的应用场景是有所侧重的。
比如,对于搜索框输入建议这个场景,理应使用防抖而非节流,理由:我们监听的是输入事件,且应该在用户输入完想要输入的内容后再给出建议,若使用节流,就可能在用户还没输入完成时给出了无效建议。
实现

防抖实现

(1)防抖开始时不调用,结束时才调用。
  1. function debounce(func, delay = 0){
  2.     let timer = null;
  3.     return function(){
  4.         clearTimeout(timer);
  5.         timer = setTimeout(()=>{
  6.             func.apply(this, arguments);
  7.         }, delay);
  8.     }
  9. }
复制代码
(2)防抖开始时立即调用,结束时也调用。
  1. // func 是待防抖函数
  2. // delay 是最短执行时间间隔,单位 ms
  3. // options 是额外配置,包括首次调用立即执行
  4. function debounce(func, delay, options = {}) {
  5.     let timer; // 计时器 ID
  6.     const { leading = false } = options; // 默认首次调用不立即执行
  7.     const debounced = function (...args) {
  8.         // 保留执行时上下文
  9.         const context = this;
  10.         // 首次调用执行
  11.         if (leading && !timer) func.apply(context, args);
  12.         
  13.         // 控制触发时间间隔的关键点
  14.         clearTimeout(timer);
  15.         timer = setTimeout(()=>{
  16.             // 避免重复执行,首次调用执行了尾部延迟结束就不再执行
  17.             if (!leading) func.apply(context, args);
  18.             
  19.             timer = null;
  20.         }, delay);
  21.     };
  22.         // 取消防抖的功能函数
  23.     debounced.cancel = () => {
  24.         clearTimeout(timer);
  25.         timer = null;
  26.     };
  27.     return debounced;
  28. }
复制代码
为什么要在更新 timer 为null前执行 clearTimeout?
更新 timer 为null不会使得timer关联的计时器失效。
节流实现

(1)时间戳版本:强调在节流开始时就会调用函数,节流结束时不调用
  1. function throttle(func, delay = 0){
  2.     let lastExecTime = 0;
  3.    
  4.     return function(){
  5.         let context = this;
  6.         let args = arguments;
  7.         let remaining = delay - (Date.now() - lastExecTime);
  8.         if(remaining < 0){
  9.             func.apply(context, args);
  10.             lastExecTime = Date.now();
  11.         }
  12.     }
  13. }
复制代码
(2)定时器版本:关注点在节流结束时再调用函数,而开始时不会调用函数
  1. function throttle(func, delay = 0){
  2.     let timer = null;
  3.    
  4.     return function(){
  5.         let context = this;
  6.         let args = arguments;
  7.         if(!timer){
  8.             timer = setTimeout(()=>{
  9.                 timer = null;
  10.                 func.apply(context, args);
  11.             }, delay);
  12.         }
  13.     }
  14. }
复制代码
(3)合并版本:结合使用时间戳和定时器,节流开始和结束时都会执行
  1. // option 配置项:
  2. //  - leading:在节流开始时执行
  3. //  - trailing:在节流结束后执行
  4. function throttle(func, delay = 0, option = {}){
  5.     let timer = null;
  6.     let lastExecTime = 0;
  7.     const { leading = true, trailing = true } = option;
  8.    
  9.     const throttled = function(...args) {
  10.         const context = this;
  11.         const remaining = delay - (Date.now() - lastExecTime);
  12.         if(leading && remaining < 0) {
  13.             lastExecTime = Date.now();
  14.             if(timer){
  15.                 clearTimeout(timer);
  16.                 timer = null;
  17.             }
  18.             func.apply(context, args);
  19.         }
  20.         else if(trailing && remaining >= 0 && !timer) {
  21.             timer = setTimeout(()=>{
  22.                 lastExecTime = Date.now();
  23.                 timer = null;
  24.                 func.apply(context, args);
  25.             }, remaining);
  26.         }
  27.     }
  28.     return throttled;
  29. }
复制代码
应用示例:


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册