节流(Throttle)

节流(Throttle)

节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。

应用场景
  • 滚动加载(滚动时每间隔一段时间检查位置)
  • 按钮点击(防止重复提交)
  • 鼠标移动(mousemove事件)
  • 游戏中的按键操作
时间戳版本(立即执行)
javascript 复制代码
function throttle(func, wait) {
  let previous = 0;
  return function () {
    const now = Date.now();
    const context = this;
    const args = arguments;

    if (now - previous > wait) {
      func.apply(context, args);
      previous = now;
    }
  };
}
定时器版本(延迟执行)
javascript 复制代码
function throttle(func, wait) {
  let timeout;
  return function () {
    const context = this;
    const args = arguments;

    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(context, args);
      }, wait);
    }
  };
}
综合版本(先立即执行,然后节流)
javascript 复制代码
function throttle(func, wait) {
  let timeout, context, args;
  let previous = 0;

  const later = function () {
    previous = Date.now();
    timeout = null;
    func.apply(context, args);
  };

  const throttled = function () {
    const now = Date.now();
    // 下次触发 func 剩余的时间
    const remaining = wait - (now - previous);
    context = this;
    args = arguments;

    // 如果没有剩余时间了或者你改了系统时间
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      func.apply(context, args);
    } else if (!timeout) {
      timeout = setTimeout(later, remaining);
    }
  };

  throttled.cancel = function () {
    clearTimeout(timeout);
    previous = 0;
    timeout = null;
  };

  return throttled;
}
ES6版本
javascript 复制代码
class Throttler {
  constructor(func, wait, options = {}) {
    this.func = func;
    this.wait = wait;
    this.options = {
      leading: true, // 是否立即执行
      trailing: true, // 是否在结束后再执行一次
    };
    Object.assign(this.options, options);

    this.timeout = null;
    this.previous = 0;
    this.args = null;
    this.context = null;
  }

  execute(...args) {
    const now = Date.now();
    this.context = this;
    this.args = args;

    // 如果是第一次调用且 leading 为 false, 设置 previous 为 now
    if (!this.previous && this.options.leading === false) {
      this.previous = now;
    }

    const remaining = this.wait - (now - this.previous);

    // 时间已到货 remaining 异常(如系统时间被调整)
    if (remaining <= 0 || remaining > this.wait) {
      if (this.timeout) {
        clearTimeout(this.timeout);
        this.timeout = null;
      }
      this.previous = now;
      this.func.apply(this.context, this.args);
    }
    // 如果 trailing 为 true, 且没有定时器
    else if (!this.timeout && this.options.trailing !== false) {
      this.timeout = setTimeout(() => {
        this.previous = this.options.leading === false ? 0 : Date.now();
        this.timeout = null;
        this.func.apply(this.context, this.args);
      }, remaining);
    }
  }

  cancel() {
    clearTimeout(this.timeout);
    this.timeout = null;
    this.previous = 0;
  }

  flush() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
      this.func.apply(this.context, this.args);
    }
  }
}

// 节流使用 - 立即执行
const throttler1 = new Throttler((msg) => {
  console.log("节流执行:", msg, Date.now());
}, 1000);

// 快速调用
throttler1.execute("队列消息1"); // 立即执行
throttler1.execute("队列消息2"); // 被忽略
setTimeout(() => throttler1.execute("队列消息3"), 1000); // 再次执行

// 节流使用 - 不立即执行
const throttler2 = new Throttler(
  (msg) => console.log("节流执行:", msg, Date.now()),
  1000,
  { leading: false, trailing: true }
);

// 手动取消
const throttler3 = new Throttler(() => {
  console.log("执行中...");
}, 500);

// 开始执行
setInterval(() => throttler3.execute(), 100);

// 2秒后取消
setTimeout(() => {
  throttler3.cancel();
  console.log("已取消节流");
}, 2000);
更通用的类实现(同时支持防抖和节流)
javascript 复制代码
class RateLimiter {
  constructor(func, wait, options = {}) {
    this.func = func;
    this.wait = wait;
    this.mode = options.mode || "throttle"; // 'throttle' 或 'debounce'

    this.options = {
      leading: true,
      trailing: true,
      maxWait: null, // 最大等待时间 (防抖专用)
    };
    Object.assign(this.options, options);

    this.timeout = null;
    this.previous = 0;
    this.args = null;
    this.context = null;
    this.lastCallTime = 0;
  }

  execute(...args) {
    const now = Date.now();
    this.context = this;
    this.args = args;
    this.lastCallTime = now;

    if (this.mode === "debounce") {
      return this._debounce(now);
    } else {
      return this._throttle(now);
    }
  }

  _debounce(now) {
    if (this.timeout) clearTimeout(this.timeout);

    // 计算延迟
    let delay = this.wait;

    // 检查是否需要立即执行
    const callNow = this.options.leading && !this.timeout;

    if (callNow) {
      this.previous = now;
      this.func.apply(this.context, this.args);
    }

    this.timeout = setTimeout(() => {
      this.timeout = null;
      if (!callNow || this.options.trailing) {
        this.func.apply(this.context, this.args);
      }
    }, delay);
  }

  _throttle(now) {
    if (!this.previous && this.options.leading === false) {
      this.previous = now;
    }

    const remaining = this.wait - (now - this.previous);

    if (remaining <= 0 || remaining > this.wait) {
      if (this.timeout) {
        clearTimeout(this.timeout);
        this.timeout = null;
      }
      this.previous = now;
      this.func.apply(this.context, this.args);
    } else if (!this.timeout && this.options.trailing !== false) {
      this.timeout = setTimeout(() => {
        this.previous = this.options.leading === false ? 0 : Date.now();
        this.timeout = null;
        this.func.apply(this.context, this.args);
      }, remaining);
    }
  }

  cancel() {
    clearTimeout(this.timeout);
    this.timeout = null;
    this.previous = 0;
  }

  flush() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
      this.func.apply(this.context, this.args);
    }
  }

  pending() {
    return !!this.timeout;
  }
}

// 使用示例
const limiter = new RateLimiter(
  (msg) => console.log(`${msg} at ${Date.now()}`),
  1000,
  { mode: "throttle" } // 或 'debounce'
);

// 切换模式
limiter.mode = "debounce";
相关推荐
Mintopia4 分钟前
一套能落地的"防 Bug"习惯:不用加班也能少出错
前端
亿元程序员6 分钟前
箭头游戏那么火,搞个3D的可以吗?我:这不是3年前的游戏了吗?
前端
IT_陈寒7 分钟前
SpringBoot里的这个坑差点让我加班到天亮
前端·人工智能·后端
巫山老妖11 分钟前
大模型工程三驾马车:Prompt Engineering、Context Engineering 与 Harness Engineering 深度解析
前端
Cobyte16 分钟前
4.响应式系统基础:从发布订阅模式的角度理解 Vue3 的数据响应式原理
前端·javascript·vue.js
晓得迷路了18 分钟前
栗子前端技术周刊第 124 期 - ESLint v10.2.0、React Native 0.85、Node.js 25.9.0...
前端·javascript·eslint
星空椰27 分钟前
JavaScript基础:运算符和流程控制
开发语言·javascript·ecmascript
半个俗人35 分钟前
fiddler的基础使用
前端·测试工具·fiddler
a11177637 分钟前
变电站数字孪生大屏ThreeJS 开源项目
前端·信息可视化·开源·html
恋猫de小郭37 分钟前
AI 的公开测评得分都在作弊,就像泡面的封面,一切以实物为准
前端·人工智能·ai编程