导读:本期聚焦于小伙伴创作的《JavaScript中如何自己实现一个节流函数来控制高频事件触发?》,敬请观看详情。在滚动、窗口缩放或输入框连续输入时,事件回调函数可能被每秒调用上百次,直接执行重计算或请求会让页面卡顿。节流函数的核心是在固定时间窗口内只让第一次或最后一次调用真正生效,其余调用被丢弃或延迟。常见实现分时间戳版与定时器版:时间戳版在时间段开始时立即执行,适合优先响应;定时器版在时间段结束时执行,保证末次触发不丢失。理解两者的差异并能合并成首尾都执行的版本,才能在实际项目中按场景选用,避免误用防抖造成交互延迟。

在前端开发中,滚动监听、鼠标移动以及窗口resize等事件会在短时间内高频触发,如果每次触发都执行复杂逻辑,容易造成主线程阻塞。节流函数的作用就是限制某个函数在一定时间间隔内最多执行一次,从而平衡响应速度与性能消耗。下面我们通过手写代码来搞清楚它的实现机制。

JavaScript中如何自己实现一个节流函数来控制高频事件触发?

什么是节流函数

节流(throttle)与防抖(debounce)不同,防抖是把多次触发合并为最后一次触发后执行,而节流是保证在连续触发过程中,每隔一段时间必然执行一次。可以理解为“匀速放行”:无论外界事件多密集,内部逻辑按照固定节奏运行。

从底层看,节流依赖两个要素:上一次执行的时间点,以及当前是否处于等待中的定时器。通过比较时间差或利用定时器状态,决定本次调用是直接执行、忽略还是延后执行。这种机制在动画计算、实时搜索联想等场景非常实用。

基于时间戳的节流实现

时间戳版是最直观的写法。我们记录上一次执行的时间,当新调用进来时,如果距离上次执行已经超过了设定的间隔,就立即执行并更新时间戳;否则什么都不做。

function throttle(fn, wait) {
  let previous = 0;
  return function() {
    const now = Date.now();
    const context = this;
    const args = arguments;
    if (now - previous >= wait) {
      fn.apply(context, args);
      previous = now;
    }
  };
}

// 使用示例
window.addEventListener('scroll', throttle(function() {
  console.log('滚动事件处理,每秒最多一次');
}, 1000));

这种写法的优点是第一次触发会立刻执行,用户操作能得到迅速反馈。但它的不足在于:如果最后一次触发距离上一次执行不到wait时间,这次调用就会被直接丢弃,可能导致末尾状态没有更新。

例如用户停止滚动时,如果停止前最后一次滚动刚好被忽略,页面的最终位置相关计算就不会跑。所以对于某些需要“收尾”的逻辑,纯时间戳版并不完美。

基于定时器的节流实现

定时器版换了一个思路:当第一次调用时,如果当前没有定时器,就设置一个定时器,在wait之后执行函数并清空定时器标记。这样既保证间隔内只排一次队,也能在尾部触发后补上执行。

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

// 使用示例
const resizeHandler = throttle(function() {
  console.log('窗口大小变化处理');
}, 500);
window.addEventListener('resize', resizeHandler);

定时器版的特点是首次触发不会立即执行,而是等待wait毫秒后才执行,但停止触发后那一次一定会跑完。它适合不需要极速首响、但必须保证末次状态落地的场景。

不过它也有缺点:如果持续触发,函数会每个wait周期执行一次,但首次延迟可能让交互感觉“慢半拍”。实际项目中经常需要结合两者优点。

时间戳与定时器结合的完整版

我们可以把前面两种方案融合:用时间戳控制首响,用定时器保证尾响。逻辑是,若时间差达标就立刻执行;若不达标且暂无定时器,则设一个定时器在剩余时间后执行。

function throttle(fn, wait) {
  let previous = 0;
  let timer = null;
  return function() {
    const now = Date.now();
    const remaining = wait - (now - previous);
    const context = this;
    const args = arguments;
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(context, args);
      previous = now;
    } else if (!timer) {
      timer = setTimeout(function() {
        fn.apply(context, args);
        previous = Date.now();
        timer = null;
      }, remaining);
    }
  };
}

// 使用示例
const scrollFn = throttle(function(e) {
  console.log('既能首响也能尾响的滚动处理');
}, 800);
document.addEventListener('scroll', scrollFn);

在这个版本里,函数既能在高频触发开头立即响应,又能在停止触发后把最后的状态补执行,兼顾了体验与准确。remaining变量动态计算还需等待的时间,避免定时器写死wait造成叠加偏移。

使用节流时要注意wait参数不能过小,否则和没节流差别不大;也不能过大,否则交互迟钝。通常滚动类设100到200毫秒,resize类设300毫秒左右较为合适。

使用场景与注意事项

节流常用于:无限滚动加载、图表实时重绘、游戏循环中的输入采样等。它和防抖的选择依据是,如果你只关心“最终态”用防抖,如果关心“过程态均匀反馈”用节流。

另外在React或Vue等框架中,如果把节流函数写在渲染函数里,每次渲染都会生成新函数导致失效,应当用useRef或模块级变量保存节流后的函数引用,才能保证计时状态连续。

实现方式首次是否立即执行末次是否保证执行适用偏好
时间戳版优先响应
定时器版保证收尾
结合版综合体验

通过上述手写过程可以看到,节流并不依赖任何外部库,理解其时间判断与闭包保存状态的原理后,就能针对业务灵活调整。掌握它有助于写出更流畅、更省资源的前端交互代码。

JavaScript节流函数throttle修改时间:2026-08-05 10:03:36

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。