一、节流介绍
节流:按照设定的频率触发回调函数,可以减少一段时间内时间的触发频率。
应用:
- 滚动事件: 当用户滚动页面时,触发的滚动事件可能会非常频繁。使用节流可以确保滚动事件处理函数不会过于频繁地执行,从而减轻浏览器的负担。
- 调整窗口大小: 当用户调整浏览器窗口大小时,窗口大小事件会频繁触发。通过节流,可以确保在一定时间内只执行一次窗口大小调整事件的处理函数。
二、功能实现
2.1基本功能
首先需要设定一个触发频率Interval
设定一个开始时间startTime和一个触发事件的时间nowTime
计算出距离触发函数的时间waitTime=interval - (nowTime - startTime)
startTime初始化为0,当第一次触发事件时,nowTime会是一个时间戳,例如1692608776,waitTime得到的结果会是一个很大的负数,所以第一次会发生调用。
调用后startTime赋值为触发的时间nowTime,所以第二次触发时,如果距离要触发的时间大于0,则不会再次触发。
function mythrotttle(fn,interval) {
let startTime = 0;
let timer = null;
const _throttle = function () {
const nowTime = new Date().getTime();
const waitTime = interval - (nowTime - startTime); //关键一步,计算出距离触发函数的时间
if (waitTime < 0) {
//this的绑定和传递参数
fn.apply(this, arguments);
startTime = nowTime;
}
};
return _throttle;
}
2.2禁用首次执行
由于startTime初始值为0,所以会造成第一次的waitTime是小于0的,所以只需要在首次触发时将startTime=nowTime,两者相减会变为0,导致结果waitTime大于0,不会触发回调。
function mythrotttle(fn,interval,immediate=true) {
let startTime = 0;
let timer = null;
const _throttle = function () {
const nowTime = new Date().getTime();
//第一次执行由于starTime=0,nowTime很大,导致减出来的值是一个负值
if (!immediate && startTime === 0) {
startTime = nowTime;
}
const waitTime = interval - (nowTime - startTime); //关键一步,计算出距离触发函数的时间
if (waitTime < 0) {
//this的绑定和传递参数
fn.apply(this, arguments);
startTime = nowTime;
}
};
return _throttle;
}
2.3尾调用
尾调用可以实现在最后一次会触发函数
尾调用处理过程:当在startTime=10s时(这里用时间错也是一样的道理),也就是10s时发生过一次回调,当用户在15s点击之后,此时距离触发回调事件=waiTime=5s,还没到触发时间,在这时候会开启一个5s的定时器,会在20s时触发。当用户之后都没有点击操作时,时间一到20s,回调就会立即触发。
为什么触发函数时需要if(timer) clearTimeout(timer); 清除定时器?
原因:举一个例子,当第一次回调触发在10s,11s点击之后开启定时器,如果在20s时触发点击,此时之前开启的定时器也会触发,为了避免触发两次,需要清除。
function mythrotttle(
fn,
interval,
{ immediate = true, trailing = false } = {}
) {
let startTime = 0;
let timer = null;
const _throttle = function () {
const nowTime = new Date().getTime();
if (!immediate && startTime === 0) {
startTime = nowTime;
}
const waitTime = interval - (nowTime - startTime);
if (waitTime < 0) {
//如果触发的话,需要清除之前的定时器,防止触发两次
if (timer) clearTimeout(timer);
fn.apply(this, arguments);
startTime = nowTime;
timer = null;
return; //如果发生调用的话,直接返回
}
//是否尾调用,开启过一次定时器就不需要开启第二次了
if (trailing && !timer) {
timer = setTimeout(() => {
fn.apply(this, arguments);
startTime = new Date().getTime(); //这里要获取触发时的时间作为最新开始时间,
timer = null;
}, waitTime); //这里是waitTime,上次调用过后,从这个调用时间开始计算,距离触发周期调用的时间(不用都是interval时间),因为要周期性触发
}
};
return _throttle;
}