深入浅析
本文将详细探讨 JavaScript 中的节流函数(Throttle),并提供四种不同实现方式的代码示例及详细解释。 节流函数用于限制函数在一定时间内执行的次数,防止函数被频繁调用导致性能问题。这在处理高频事件(例如滚动事件、鼠标移动事件等)时非常有用。
想象一下沙漏:沙子不断流入,但流出的沙子数量有限。节流函数的工作原理类似,它控制函数执行的频率,避免函数被瞬间大量调用。
节流函数的典型应用场景包括:
- 鼠标点击事件: 防止用户快速连续点击导致多次执行同一操作。
- 滚动事件: 在页面滚动时,节流可以限制加载更多内容的请求频率,避免频繁请求造成服务器压力。
- 输入框监听: 限制搜索或自动完成请求的频率,优化用户体验。
节流与防抖的差异
在之前的文章中,我们介绍了防抖函数(Debounce)。节流与防抖虽然都能降低函数执行频率,但它们的工作机制有所不同:
- 节流: 在规定时间间隔内,无论事件触发多少次,函数最多只执行一次。 时间间隔主要指两次函数执行之间的时间差。
- 防抖: 在事件停止触发后的一个规定时间内,函数只执行一次。 时间间隔则指连续触发事件的持续时间。
四种节流函数实现方式
我们将分别介绍四种常见的节流函数实现:时间戳版、定时器版、组合版以及高级自定义版。
1. 时间戳版节流
这种方法通过记录上一次函数执行的时间戳来判断是否需要执行函数。它能保证函数在规定时间间隔后立即执行。
const throttle = (func, wait) => {
// 初始化事件开始的时间为0
let preTime = 0;
return function() {
// 下面两行不懂的可以看看防抖实现的那篇文章
let context = this;
let args = arguments;
// 获取当前的时间,使用+来转化为数字类型,方便后面做减法
let now = +new Date();
// 当前时间减去之前的时间,结果大于设定的间隔时间才会执行函数
if (now - preTime > wait) {
func.apply(context, args);
preTime = now;
}
}
};
2. 定时器版节流
该方法利用setTimeout
来控制函数的执行。 函数不会立即执行,而是在规定时间间隔后执行。通常用于在事件停止触发后执行一次操作。
const throttle2 = (func, wait) => {
let timeout;
return function() {
let context = this;
let args = arguments;
// 若没有定时器,说明上一次设定的定时器已到时销毁
if (!timeout) {
timeout = setTimeout(function() {
func.apply(context, args);
timeout = null;
}, wait)
}
}
};
3. 组合版节流
结合时间戳版和定时器版,可以实现更灵活的节流:既可以在时间间隔到期时立即执行,又可以在事件停止触发后执行一次。
function throttle3(func, wait){
let context, args, timeout;
let pretime = 0;
let later = function(){
pretime = +new Date();
timeout = null;
func.apply(context, args);
};
let throttled = function(){
context = this;
args = arguments;
var now = +new Date();
var remaining = wait - (now - pretime);
// 剩余时间为负数表示下一次执行需要立即执行
// remaining > wait在修改了系统时间的情况下可能发生
if(remaining <= 0 || remaining > wait){
// 如果有设置过定时器,清空并置为null
if(timeout){
clearTimeout(timeout)
timeout = null;
}
pretime = now;
func.apply(context,args);
}else if(!timeout){
// 需要在剩余时间后执行
timeout = setTimeout(later,remaining);
}
};
return throttled;
};
4. 高级自定义版节流
此版本允许通过配置参数 options
来控制函数的执行时机,更加灵活:
leading: false
:禁用首次执行。trailing: false
:禁用最后一次执行。
/**
* 高级自定义节流函数,限制函数在指定时间段内最多执行一次。
*
* @param {Function} func - 要节流的函数。
* @param {number} wait - 节流时间间隔(毫秒)。
* @param {object} [options] - 可选参数。
* @param {boolean} [options.leading=true] - 是否允许在节流时间段开始时立即执行函数。
* @param {boolean} [options.trailing=true] - 是否允许在节流时间段结束时执行函数。
* @returns {Function} 节流后的函数。该函数包含一个 `cancel` 方法用于取消任何待定的执行。
*/
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
const later = function () {
previous = options.leading === false ? 0 : now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
const throttled = function () {
var _now = now();
if (!previous && options.leading === false) previous = _now;
var remaining = wait - (_now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = _now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}
这个版本还包含cancel
方法,用于取消任何待定的执行。
总结
本文详细介绍了四种 JavaScript 节流函数的实现方式,从简单的计时器方法到更高级的自定义版本,满足不同的应用场景。 选择合适的节流函数实现方式,能够有效提高程序性能,优化用户体验。 建议根据实际需求选择合适的实现方式。 高级自定义版本与流行的库(如 underscore)中的节流函数实现类似。