Skip to content

节流和防抖

防抖(debounce)

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

javascript
// 防抖函数
function debounce(fn, wait) {
  let timer;
  return function () {
    let _this = this;
    let args = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(function () {
      fn.apply(_this, args);
    }, wait);
  };
} // 使用window.onresize = debounce(function () {  console.log("resize");}, 500);

这段代码实现了 JavaScript 中的防抖(debounce) 函数,用于限制函数在短时间内的频繁执行。下面从基础到深入,逐行解析其中的知识点:

1. 防抖(Debounce)的核心概念 防抖的作用是:当事件被频繁触发时,只在最后一次触发后的指定时间(wait)后执行函数
例如:输入框搜索联想(停止输入 wait 毫秒后再发送请求)、窗口 resize 事件(调整结束后再计算布局)。

2. 函数结构解析

javascript
function debounce(fn, wait) { ... }
  • debounce 是一个高阶函数(接收函数作为参数,并返回新函数)。
  • 参数:
    • fn:需要防抖处理的原函数(如搜索请求函数)。
    • wait:防抖等待时间(毫秒),即触发后等待多久才执行 fn

3. 闭包与定时器变量 timer

javascript
let timer; // 定时器变量,存储 setTimeout 的返回值
  • timer 定义在 debounce 函数内部,被返回的匿名函数引用,形成闭包
  • 作用:在多次调用防抖函数时,共享同一个 timer 变量,用于清除上一次的定时器。

4. 返回的防抖处理函数

javascript
return function () { ... }
  • 返回一个新函数(防抖处理后的函数),替代原函数执行。
  • 这个函数会在事件触发时被调用(如输入框的 input 事件、按钮的 click 事件)。

5. 保存上下文与参数

javascript
let _this = this; // 保存当前上下文(this 指向)
let args = arguments; // 保存函数参数
  • this 指向问题
    原函数 fn 可能依赖调用时的上下文(如 DOM 事件中,this 指向触发事件的元素)。这里用 _this 保存返回函数被调用时的 this,确保 fn 执行时上下文正确。

  • arguments 对象
    保存返回函数被调用时接收的所有参数(如事件对象 event),确保传递给原函数 fn

6. 清除上一次的定时器

javascript
if (timer) {
  clearTimeout(timer); // 清除已有定时器
}
  • 每次触发事件时,先清除上一次设置的定时器,避免原函数被提前执行。
  • 例如:输入框快速输入时,每次按键都会清除上一次的定时器,直到停止输入后,最后一次定时器才会生效。

7. 设置新的定时器

javascript
timer = setTimeout(() => {
  fn.apply(_this, args); // 延迟执行原函数
}, wait);
  • 重新设置定时器,wait 毫秒后执行回调函数。
  • 回调中通过 fn.apply(_this, args) 调用原函数:
    • apply 确保 fnthis 指向 _this(正确的上下文)。
    • args 作为参数传递给 fn(确保参数完整)。

8. 完整执行流程示例 以输入框搜索为例,假设 wait = 500ms,用户快速输入 "abc":

  1. 输入 "a" → 触发事件 → timerundefined → 设置定时器(500ms 后执行搜索)。
  2. 300ms 后输入 "b" → 触发事件 → 清除上一次定时器 → 重新设置定时器(500ms 后执行)。
  3. 又过 300ms 后输入 "c" → 触发事件 → 清除上一次定时器 → 重新设置定时器。
  4. 停止输入,500ms 后定时器触发 → 执行搜索函数(仅执行一次,参数为 "abc")。

9. 可能的优化点

  • 立即执行版:添加 immediate 参数,控制是否在第一次触发时立即执行,后续触发才防抖。
  • 返回值处理:原函数 fn 的返回值无法直接获取(因被包裹在 setTimeout 中),需用回调或 Promise 处理。
  • 类型定义:在 TypeScript 中添加类型注解,提升类型安全性。

节流(throttle)

高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

javascript
// 方式1: 使用时间戳
function throttle1(fn, wait) {
  let time = 0;
  return function () {
    let _this = this;
    let args = arguments;
    let now = Date.now();
    if (now - time > wait) {
      fn.apply(_this, args);
      time = now;
    }
  };
}
// 方式2: 使用定时器
function thorttle2(fn, wait) {
  let timer;
  return function () {
    let _this = this;
    let args = arguments;
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        fn.apply(_this, args);
      }, wait);
    }
  };
}

上面节流防抖实现方式比较简单,但是已经可以满足日常使用,如果想更近一步了解可以查看underscorelodash文档中debouncethorttle