throttle是什么鬼?
throttle的本意是节流阀,是一种通过改变节流截面或者节流长度来控制液体流量的装置。而在前端开发中,throttle一般用来控制在某段时间间隔内耗时操作只被触发一次。比如说有这样的场景,有个列表,没有所谓的提交操作,用户在点击列表的某一项之后需要通过ajax将用户当前点击的项传给后台进行相应处理,很明显,ajax是个耗时的操作,如果我们直接设置用户点击列表的某项后直接发ajax的话那么很有可能会在短时间内发送大量的ajax(没法排除某些用户玩心大起,突然就想快速点击很多列表项,比如说我这种人,O(∩_∩)O哈哈~)
不同的throttle实现
直接用setTimeout控制函数执行次数(实现简单粗暴,很容易理解)
function throttle(func, interval = 500) {
let timer;
return function () {
const that = this,
args = arguments;
if (timer) {
return false;
}
timer = setTimeout(function () {
func.apply(that, args);
clearTimeout(timer);
timer = null;
}, interval);
}
}
// 控制每300ms打印日志的操作最多被执行一次
window.onresize = throttle(function () {
console.log('hello world');
}, 300);
复制代码
首次触发直接执行,不用等待指定时间再执行(和第一种基本上差不多,就是加了个firstTime用来区别是否首次执行而已)
function throttle(func, interval = 500) {
let timer,
firstTime = true;
return function () {
const that = this,
args = arguments;
if (firstTime) {
firstTime = false;
return func.apply(that, args);
}
if (timer) {
return false;
}
timer = setTimeout(function () {
func.apply(that, args);
clearTimeout(timer);
timer = null;
}, interval);
}
}
// 控制每300ms打印日志的操作最多被执行一次,首次触发马上执行
window.onresize = throttle(function () {
console.log('hello world');
}, 300);
复制代码
上面两种实现基本上能够满足我们大部分需求了,那如果我并不想最后一次被执行怎么办?比如说我们为keyup事件绑定第二个版本(首次立即执行)的throttle,然后我总共就按下了两个字符,那么按照第二种实现方式在我按完第二个字符interval时间之后函数又会再触发一次,这可能并不是我们想要的。那怎么修改throttle才能够实现这种效果呢?且看throttle的第三个版本。
function throttle(func, interval = 500) {
let timer,
firstTime = true,
previousExecuteTime,
now;
return function () {
now = Date.now();
const that = this,
args = arguments;
if (firstTime) {
firstTime = false;
previousExecuteTime = Date.now();
return func.apply(that, args);
}
if (timer) {
return false;
}
timer = setTimeout(function () {
if (now - previousExecuteTime >= interval) {
func.apply(that, args);
clearTimeout(timer);
timer = null;
previousExecuteTime = now;
} else {
firstTime = true;
timer = null;
}
}, interval);
}
}
// 用户首次调整窗口时打印日志,若从上次执行到调整窗口结束时间不到interval时间则不触发函数执行
window.onresize = throttle(function () {
console.log('hello world');
}, 2000);
复制代码
从第三种实现方式来看,第二种实现方式其实有个问题,那就是firstTime标志在执行过一次之后永远都是false,这样的话再次触发事件其实也就没有首次触发执行功能了。所以综合来看还是第三种实现方式好点,如果想让最后一次操作被执行的话也可以给第三种实现方式加个参数leading,如果为true的话就执行,否则不执行
if (leading || (now - previousExecuteTime >= interval))
复制代码
结束语
从开始决定每周输出一篇技术文章到现在也有挺长时间了,个人觉得主要有以下两方面的收获:一方面是对于自己真正花时间去研究,去用输出倒逼输入的技术有了更深一步的认识,使用起来也更加得心应手;另一方面有时候自己写的文章得到别人的喜欢,点赞觉得很开心,想要持续学习,继续输出高质量的文章和大家一起进步。