防抖/节流的优缺点分析和改良升级

本文介绍了JavaScript中防抖和节流的概念及其应用场景,如搜索框输入、鼠标移动和窗口大小变化事件。防抖通过限制执行频率避免过多计算,而节流则基于时间间隔决定是否执行。文章还提出了防抖和节流的组合版,以兼顾触发频率和最后一次执行。并提供了三种技术的实现代码,通过实例展示了它们在实际操作中的效果,验证了防抖可能导致最后触发延迟,节流可能丢失最后一次触发的问题,而组合版则解决了这两个问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 防抖和节流的作用:
    防止高频的事件触发造成不必要的计算消耗, 比如, 典型的应用场景:

  •     搜索框 input事件;
  •     鼠标移动 mousemove事件;
  •     视窗大小变化 resize事件;

二. 防抖的优缺点:
    创建timer, 当新的触发出现时, 销毁老的timer.
    性能消耗稍大于节流(不过, 这点性能差别算不了什么).
    如果连续的调用间隔一直小于delay, 会造成长时间不触发.

三. 节流的优缺点:
    通过时间差来判断要不要放弃当次触发,
    不需要创建和销毁timer. 
    但是因为根据前一次的时间差来判断放弃, 可能会扔掉最后一次.
     如果最后一次包含重要的状态切换, 则可能丢失.

四. 防抖+节流的升级版
    在防抖的基础上增加一个值, 用于记住上次的触发时间, 
    采用上次触发时间到当前时间的差, 用于计算真实的delay大小.
    当delay小到等于0的时候, clearTimeout就没有机会清掉前一次触发了.

五. 代码实现和测验:

function debounce(cb, delay) {                    // 防抖(回调, 延迟)
  let timer = null;                               // timer对象
  return function(...args) {                      // 返回闭包给外部调用, 收集外部调用的参数
    if(timer !== null) clearTimeout(timer);       // 先清除前一次timer
    timer = setTimeout(() => {                    // 设置delay timer
      cb && cb.apply(this, args);                 // 将外部参数传给回调
    }, delay);
  }
}

function throttle(cb, delay){                     // 节流(回调, 延迟)
  let lastTick = 0;                               // 上次发送时间点
  return function(...args) {                      // 返回闭包函数, 取到外部参数
    if (Date.now() - lastTick > delay) {          // 时间差大于delay, 才调用fn
        lastTick = Date.now();                    // 更新时间
        cb && cb.apply(this, args);               // 将外部参数传给回调
    }
  }
}

function debounce_throttle(cb, delay) {               // 防抖+节流(回调, 延迟)
  let timer = null;                                   // timer对象
  let lastTick = Date.now();                          // 上次发送时间
  return function(...args) {                          // 返回闭包给外部调用, 收集外部调用的参数
    if(timer !== null) clearTimeout(timer);           // 先清除前一次timer
    timer = setTimeout(() => {                        // 设置delay timer
      lastTick = Date.now();                          // 更新时间
      cb && cb.apply(this, args);                     // 将外部参数传给回调
    }, Math.max(0, delay - (Date.now() - lastTick))); // delay减去已消耗时间
  }
}

function A(a) {
  this.a = a;
  this.proc = function(...args) {
    console.log(`[${Date.now()/1000}] A.proc, this:${this.a}, args:${JSON.stringify(args)}`);
  }
}
let aa = new A(123);
let bb = new A(456);
let cc = new A(789);

let db = debounce(aa.proc.bind(aa), 1000);
let tt = throttle(bb.proc.bind(bb), 1000);
let dt = debounce_throttle(cc.proc.bind(cc), 1000);

let startTick = Date.now();
let trigger = setInterval(()=>{
  console.log(`[${Date.now()/1000}] interval call`);
  db(1,2,3);
  tt(4,5,6);
  dt(7,8,9);
  if (Date.now() - startTick > 4000) clearInterval(trigger);
}, 200);

六. 测验输出结果:

> node .\test.js
[1616485874.114] interval call
[1616485874.121] A.proc, this:456, args:[4,5,6]
[1616485874.315] interval call
[1616485874.516] interval call
[1616485874.716] interval call
[1616485874.914] A.proc, this:789, args:[7,8,9]
[1616485874.917] interval call
[1616485875.117] interval call
[1616485875.317] interval call
[1616485875.318] A.proc, this:456, args:[4,5,6]
[1616485875.518] interval call
[1616485875.717] interval call
[1616485875.914] A.proc, this:789, args:[7,8,9]
[1616485875.919] interval call
[1616485876.118] interval call
[1616485876.319] interval call
[1616485876.319] A.proc, this:456, args:[4,5,6]
[1616485876.519] interval call
[1616485876.72] interval call
[1616485876.914] A.proc, this:789, args:[7,8,9]
[1616485876.92] interval call
[1616485877.121] interval call
[1616485877.32] interval call
[1616485877.321] A.proc, this:456, args:[4,5,6]
[1616485877.521] interval call
[1616485877.722] interval call
[1616485877.915] A.proc, this:789, args:[7,8,9]
[1616485877.923] interval call
[1616485878.915] A.proc, this:789, args:[7,8,9]
[1616485878.937] A.proc, this:123, args:[1,2,3]
>

从结果看, 防抖因为连续触发的间隔小于设定的delay, 所以直到最后才获得触发机会.
节流前期正常触发, 遗落了最后一次触发.
只有第三个版本, 即能均匀触发, 又不丢失最后一次触发. 

### 防抖节流的概念 防抖(Debouncing)是指当某个事件被触发时,在一定时间内如果该事件再次被触发,则重新计时,只有在指定的时间间隔内不再触发此事件时才会执行对应的回调函数[^1]。这种机制可以有效减少高频事件的调用次数。 节流(Throttling)则是指在一个特定时间范围内只允许某件事情发生一次或者有限几次。即使事件频繁触发,也会按照设定好的固定频率来执行相应的逻辑[^2]。 --- ### 防抖节流的区别 两者的根本区别在于它们控制事件触发的方式不同: - **防抖**关注的是最后一次触发后的延迟效果,即等待一段时间后再执行操作。 - **节流**则是在连续多次触发的情况下,按固定周期执行动作,忽略多余的触发请求[^4]。 --- ### 防抖的具体实现方式 以下是基于 JavaScript 的一种常见防抖函数实现方法: ```javascript function debounce(func, delay) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => func.apply(this, args), delay); }; } ``` 上述代码通过 `setTimeout` `clearTimeout` 来管理定时器对象 `timer`,从而确保每次新事件到来都会清除之前的延时任务并设置新的延时任务[^3]。 #### 使用示例 假设我们需要监听窗口大小变化,并希望仅在调整结束后才更新布局: ```javascript window.addEventListener('resize', debounce(function () { console.log('Window resized'); }, 300)); ``` --- ### 节流的具体实现方式 下面是一个典型的节流函数定义: ```javascript function throttle(func, limit) { let lastCall = 0; return function(...args){ const now = new Date().getTime(); if(now - lastCall >= limit){ lastCall = now; func.apply(this, args); } }; } ``` 这里利用当前时间上一次成功运行之间的时间差判断是否应该继续执行目标功能。 #### 应用实例 对于滚动条位置监控来说,我们可以这样写: ```javascript window.addEventListener('scroll', throttle(function() { console.log('Scrolling...'); }, 200)); ``` --- ### 实际应用场景分析 无论是防抖还是节流都广泛应用于各种前端开发场景之中,具体如下表所示: | 功能需求 | 推荐方案 | |----------|----------------| | 输入框自动补全提示 | 防抖 | | 页面无限加载更多数据 | 节流 | | 缩放浏览器视窗尺寸检测 | 防抖/节流均可考虑依据业务复杂度决定优先级 | 例如,在输入框实时查询建议列表的时候采用防抖策略能够显著降低服务器压力;而针对持续性的滚屏行为监测更适合运用节流手段以维持流畅体验同时节省资源开销。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值