节流(throttle) 是一种优化高频触发事件的技术
在 JavaScript 中,节流(throttle) 是一种优化高频触发事件的技术,它能保证函数在一定时间间隔内最多只执行一次,避免因事件频繁触发(如滚动、 resize、鼠标移动等)导致的性能问题。
节流的核心原理
- 设定一个时间间隔(如 100ms)
- 当事件触发时,先检查距离上一次执行是否超过该间隔
- 如果超过,则执行函数并更新“上一次执行时间”
- 如果未超过,则忽略本次触发
实现方式
常见的节流实现有两种方式:时间戳版和定时器版,也可以结合两者实现更完善的版本。
1. 时间戳版(立即执行)
特点:事件触发时立即执行一次,之后在间隔内不重复执行,间隔结束后才会再次响应。
function throttle(fn, interval) {let lastTime = 0; // 记录上一次执行的时间戳return function (...args) {const now = Date.now(); // 当前时间戳// 如果当前时间 - 上一次执行时间 >= 间隔,则执行函数if (now - lastTime >= interval) {fn.apply(this, args); // 绑定上下文和参数lastTime = now; // 更新上一次执行时间}};
}
2. 定时器版(延迟执行)
特点:事件触发时不会立即执行,而是等待间隔时间后执行;如果间隔内再次触发,会重置定时器(最终只执行最后一次)。
function throttle(fn, interval) {let timer = null; // 定时器标识return function (...args) {// 如果没有定时器,则创建一个if (!timer) {timer = setTimeout(() => {fn.apply(this, args);timer = null; // 执行后清空定时器,允许下次触发}, interval);}};
}
3. 综合版(立即+延迟)
结合两种方式的优点:首次触发立即执行,间隔内再次触发会在间隔结束后补充执行一次(避免最后一次触发被忽略)。
function throttle(fn, interval) {let lastTime = 0;let timer = null;return function (...args) {const now = Date.now();const remaining = interval - (now - lastTime); // 剩余时间// 如果剩余时间 <= 0,说明已超过间隔,立即执行if (remaining <= 0) {if (timer) {clearTimeout(timer);timer = null;}fn.apply(this, args);lastTime = now;} // 否则,设置定时器,确保间隔结束后执行一次else if (!timer) {timer = setTimeout(() => {fn.apply(this, args);lastTime = Date.now(); // 更新时间为定时器执行时的时间timer = null;}, remaining);}};
}
使用示例
以滚动事件为例,限制函数 100ms 内最多执行一次:
// 定义需要节流的函数
function handleScroll() {console.log('滚动事件执行了', Date.now());
}// 应用节流(间隔 100ms)
const throttledScroll = throttle(handleScroll, 100);// 绑定到滚动事件
window.addEventListener('scroll', throttledScroll);
注意事项
- 上下文绑定:使用
apply(this, args)确保函数执行时的this指向正确(如事件触发元素)。 - 参数传递:通过
...args接收并传递事件参数(如event对象)。 - 场景选择:
- 时间戳版适合需要立即响应的场景(如实时计算滚动位置)。
- 定时器版适合需要延迟处理的场景(如输入框搜索联想)。
- 综合版适合需要兼顾首次立即执行和最后一次延迟执行的场景(如窗口 resize 后调整布局)。
节流与防抖(debounce)的区别:
- 节流:控制执行频率(固定间隔内必执行一次)。
- 防抖:合并多次触发为一次(只有当事件停止触发一段时间后才执行)。
