防抖/节流
一、什么是防抖和节流?
防抖:
当频繁地执行一个函数,只需在最后一次执行该函数后一个间隔时间触发一次该函数,即在一些应用场景中,如input框的模糊查询,其特点:
- 持续触发不执行函数;
- 最后一次触发完毕,后隔一段时间只执行一次该函数;
节流
当频繁的执行一个函数,不管触发该事件如何频繁,只在间隔指定时间触发一次该事件,触发仍旧是多次,但是间隔时间是认为设定的,其特点:
- 持续频繁触发,不会按照触发频次执行;
- 持续触发,指按照设定间隔时间执行,执行是多次的,达到节制频率的作用;
二、debounce函数
// 触发事件对应的函数
function counter() {
content.innerHTML = num++;
};
// 防抖函数
function debounce(func, delay) {
let timer = null;
return function() {
if(timer) clearTimeout(timer)
// 如果持续触发,那么就清除定时器,定时器的回调就不会执行。
timer = setTimeout(() => {
// 通过apply方法改变this的作用域到func上,参数为该函数的argument参数对象
func.apply(this, arguments)
}, delay)
}
}
// 截流函数调用
content.onmousemove = debounce(counter,1000);
通过闭包的方式实现该防抖函数,
debounce(counter,1000)
的两个参数分别表示触发事件函数和触发间隔时间,最后注册到onmousemove事件上的函数,是debounce内部函数;
三、throttle函数
1.时间戳节流函数
- 特点:只要当前点击时,距离上次时间触发事件间隔大于或者等于delay时间,就立即执行fn函数
// 触发事件对应的函数
function counter() {
content.innerHTML = num++;
};
// 节流函数
function throttle(fn, delay) {
// 此处last参数在外部函数执行完毕会释放掉,但是在内部函数的作用域链中仍然保留其变量对象;
// 在内部函数赋值last之前,last变量都是不变的
let last = 0
return function () {
let now = Date.now()
if (now - last >= delay) {
last = now;
// 利用apply方法指定fn函数的作用域,闭包中匿名函数作用域指向为window
// 内部函数返回后,最后被window调用,apply方法指定作用域,同时将arguments参数代入
fn.apply(this, arguments)
}
}
}
// 节流函数用法
content.onmousemove = throttle(counter,1000);
注意: 第一次点击的时候,事件必触发,因为last = 0
,所以第一次会满足触发条件
2.定时器节流函数
// 触发事件对应的函数
function counter() {
content.innerHTML = num++;
};
// 节流函数
function throttle(fn, delay) {
let timer = null
return function () {
let context = this;
let args = arguments;
if(!timer){
timer = setTimeout(function(){
fn.apply(context,args)
timer = null;
},delay)
}
}
}
// 节流函数用法
content.onmousemove = throttle(counter,1000);Ï
注意: 最后一次点击时候,事件也会隔1秒才执行;
3.综合定时器和时间戳
要求: 第一次点击不触发事件,最后一次点击立刻触发事件;
可以结合时间戳和定时器方法,即在首次点击的时候用定时器方法,在后面的点击事件中使用时间戳方法;
如下,remainning>0时,定时器方法会被执行,但是延时为remianning,因此每次点击都会clearTimeout(timer),实际最后执行的还是remianning<=0时的函数;
此时remainning<=0对应的是时间戳函数;于是只有在首次才会执行定时器函数;
function throttle(fn, delay) {
// 设置定时器
let timer = null;
// 设置时间戳,表示每次点击事件的时间
let startTime = Date.now();
return function () {
// 当前时间
let curTime = Date.now();
// delay为节流时间间隔,curTime为当前时间,startTime此时表示上次的事件执行时间
let remainning = delay -(curTime - startTime)
let context = this;
let args = arguments;
clearTimeout(timer)
if (remainning <= 0) {
// 如果此时点击时间和上次触发事件时间间隔大约等于delay时间,则执行fn函数;
// 并重新给startTime赋值新的触发事件时间
fn.apply(context, arguments)
startTime = Date.now();
} else {
timer = setTimeout(fn, remainning);
}
}
}
总结
- 节流防抖函数通过闭包,将内部函数return出去,给到window去调用,注意window最后调用的是内部函数,而外部函数在第一次执行完毕,外部函数所占内存就被释放掉,但是因为作用域链特性,内部函数仍然可以访问外部函数的变量对象,并且可以修改变量值,因此外部函数中的变量可以用来存储上一次点击事件触发的时间