js函数节流(非setTimeout方案)
只记得js函数节流可以使用setTimeout的方案,那么有没有其他的方案呢?
时间戳
思路:每次触发的时候与前一次触发时刻作比较,超过时间则触发,没有则等待下一次触发。
function throttle(fn, delay) {
let prev = 0;
return function() {
let now = +new Date(),
context = this;
if (now-prev >= delay) {
fn.apply(context, arguments);
previous = now;
}
}
}
首先,一开始会触发下,接下的过程中,每次对比触发时间。
定时器版
setTimeout方案可以查看函数去抖动和节流。
大致如下:
function throttle(fn, delay) {
let timer = null;
return function() {
let context = this,
args = arguments;
if(!timer) {
timer = setTimeout(function() {
fn.apply(context, args);
timer = null;
}, delay);
}
}
}
两者区别
时间戳的方式,会提前先执行一遍,等后期有连续调用时再检测时间做触发判断。
定时器的方式,调用后等待相应时间后再触发,也就是说,当调用停止后,也会触发最后一次。
两者结合
underscore中提供了throttle方法,是将两者结合的方式,删掉些无关紧要的东西,如下:
function throttle(func, wait) {
var timeout, context, args, result;
var previous = 0;
// 延时函数
var later = function() {
previous = +new Date();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = +new Date();
var remaining = wait - (now - previous); // 剩余时间
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
// 超出时间,直接触发,清除原先的定时器
// 这块属于纠正,当出现修改pc时间这种问题也能处理掉,
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
// 记录时间戳
previous = now;
// 触发处理
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout) {
// 正常的延时触发
timeout = setTimeout(later, remaining);
}
return result;
};
// 节流取消
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};
我们可以只关注throttled函数,其中remaining的方式较好的处理了多种异常情况,例如,
- setTimeout需要前一次时间循环的宏任务执行完后才会运行的偏差,此时表现为remaining值小于0。
- 修改pc时间这种问题,会提前执行方法,表现为remaining值大于wait。
underscore中的原方法还支持设置options为{leading: false},可以关掉首次函数调用触发的问题。
地址:underscore.js,左侧也提供了简单的代码说明。