Open sisterAn opened 4 years ago
function throttle( fn, delay ) {
let prevDate = 0;
return function () {
let context = this;
let nowDate = new Date();
if(nowDate - prevDate > delay){
fn.apply(context, args, argments);
prevDate = nowDate;
}
}
function throttle (func, wait) {
let prev = 0;
return function (...args) {
const now = Date.now();
const context = this;
if (now - prev > wait) {
func.apply(context, args);
prev = now
}
}
}
leading:
| | | | | |
a b c d e f
--------------------------->
a c d e f
a 属于第一轮首次直接执行, f 距离上次执行已超过一个单位时间, 属于新一轮首次直接执行
no leading:
| | | | | |
a b c d e f
--------------------------->
c e f
a 属于第一轮首次没有直接执行, 而是延迟一个单位时间执行, 一个单位时间期间又有 b c 抢占, 所以一个单位时间后执行 c, 同理在 d 延迟一个单位时间后被 e 抢占, 所以执行 e , f 在延迟一个单位时间后未被抢占就直接执行
trailing:
| | | | | |
a b c d e fg
--------------------------->
a c d e f g
no trailing:
| | | | | |
a b c d e fg
--------------------------->
a d e f
在一个单位时间内, 首次会被执行, 但尾部执行会被抛弃掉, 如 c g
/**
* leading: 是否执行首次
* trailing: 是否执行尾次
*/
function throttle(fn, threshhold = 250, leading = true, trailing = true) {
let lastTime = 0;
let timer;
const cancel = () => {
lastTime = 0;
if (timer) {
clearTimeout(timer);
timer = null;
}
};
const throttled = (...args) => {
lastTime = !leading ? lastTime || Date.now() : lastTime;
const rest = threshhold - (Date.now() - lastTime);
if (rest <= 0) {
cancel();
lastTime = Date.now();
fn(...args);
} else if (timer == null && trailing) {
timer = setTimeout(() => {
lastTime = leading ? Date.now() : 0;
timer = null;
fn(...args);
}, rest);
}
};
throttled.flush = (...args) => {
if (timer) {
cancel();
fn(...args);
}
};
throttled.cancel = cancel;
return throttled;
}
/**
* 2. 防抖函数
*/
function debounce(callback, time) {
let timer=null;
return function(){
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(callback,time);
}
}
/**
* 3. 节流
*/
function throttle(name,time) {
return new Promise((resolve,reject)=>{
if(this[name]) {
return false;
}
this[name] = true;
setTimeout(()=>{
this[name] = false;
console.log('点击成功')
},time);
resolve(this[name]);
})
}
debounce
与throttle
是开发中常用的高阶函数,作用都是为了防止函数被高频调用,换句话说就是,用来控制某个函数在一定时间内执行多少次。使用场景
比如绑定响应鼠标移动、窗口大小调整、滚屏等事件时,绑定的函数触发的频率会很频繁。若稍处理函数微复杂,需要较多的运算执行时间和资源,往往会出现延迟,甚至导致假死或者卡顿感。为了优化性能,这时就很有必要使用
debounce
或throttle
了。debounce
与throttle
区别防抖 (debounce) :多次触发,只在最后一次触发时,执行目标函数。
节流(throttle):限制目标函数调用的频率,比如:1s内不能调用2次。
手写一个
throttle
实现方案有以下两种:
scroll
事件刚触发时,打印一个 hello world,然后设置个1000ms
的定时器,此后每次触发scroll
事件触发回调,如果已经存在定时器,则回调不执行方法,直到定时器触发,handler
被清除,然后重新设置定时器。这里我们采用第一种方案来实现,通过闭包保存一个
previous
变量,每次触发throttle
函数时判断当前时间和previous
的时间差,如果这段时间差小于等待时间,那就忽略本次事件触发。如果大于等待时间就把previous
设置为当前时间并执行函数 fn。我们来一步步实现,首先实现用闭包保存
previous
变量。执行
throttle
函数后会返回一个新的function
,我们命名为betterFn
。betterFn
函数中可以获取到previous
变量值也可以修改,在回调监听或事件触发时就会执行betterFn
,即betterFn()
,所以在这个新函数内判断当前时间和previous
的时间差即可。结合上面两段代码就实现了节流函数,所以完整的实现如下。
underscore 源码解读
上述代码实现了一个简单的节流函数,不过
underscore
实现了更高级的功能,即新增了两个功能leading
参数,false
时忽略)trailing
参数,false
时忽略)配置
{ leading: false }
时,事件刚开始的那次回调不执行;配置{ trailing: false }
时,事件结束后的那次回调不执行,不过需要注意的是,这两者不能同时配置。所以在
underscore
中的节流函数有 3 种调用方式,默认的(有头有尾),设置{ leading: false }
的,以及设置{ trailing: false }
的。上面说过实现throttle
的方案有 2 种,一种是通过时间戳判断,另一种是通过定时器创建和销毁来控制。第一种方案实现这 3 种调用方式存在一个问题,即事件停止触发时无法响应回调,所以
{ trailing: true }
时无法生效。第二种方案来实现也存在一个问题,因为定时器是延迟执行的,所以事件停止触发时必然会响应回调,所以
{ trailing: false }
时无法生效。underscore
采用的方案是两种方案搭配使用来实现这个功能。参考
【进阶 7-1 期】深入浅出节流函数 throttle
浅谈节流与防抖