一.防抖
1.解释
用于限制函数的执行频率,一定时间内多次触发同一个事件,只执行最后一次操作
2.应用
- 搜索框中输入内容时,不会立即发送请求获取相关数据,而是等到文字输完后的一定时间向后端发请求显示出结果
3.手写防抖
(1)这个防抖函数要接收两个参数
- 传入的执行函数fun
- 延迟的时间delay(ms)
(2)防抖函数会返回一个函数,利用setTimeout延迟一段时间后调用传入的fun
(3)在每次定时器开始之前清空之前的定时器
function debounce(fun,delay) {
let timeoutID
//返回一个函数
return function(){
//清除之前的定时器
clearTimeout(timeoutID)
//当 delay 时间到时,执行 fun
timeoutID = setTimeout(fun,delay)//传入fun和delay,setTimeout会返回一个定时器id用于清楚定时器
}
}
(4)this的指向与之前一致+正确接收参数(event)
function debounce(fun, delay) {
let timeoutID;
//return返回的这个函数才是真正被调用的函数
return function () {
if (timeoutID) {
clearTimeout(timeoutID);
}
timeoutID = setTimeout(() => {
//改变this指向并把function的参数传给fun
fun.apply(this, arguments);//fun的this的指向要与没加防抖时一致
}, delay);
};
}
(5)增加立即执行机制
- 根据timeoutID是否为空来判断是不是第一次执行
- 要满足第一次立即执行过后,要在delay的时间过后再执行
function debounce(fun, delay, immediate) {
let timeoutID;
//return返回的这个函数才是真正被调用的函数
return function () {
if (timeoutID) clearTimeout(timeoutID);
if (immediate) {
let action = !timeoutID;
//设置immediate为true的情况下,在第一次过后到delay的这段时间内timeoutID都是有值的,就可以使得action为false,不会一直触发立即执行 ==> 即下一次触发函数最少要等到delay的时间后
timeoutID = setTimeout(() => {
timeoutID = null;
}, delay);
// 立即执行
if (action) fun.apply(this, arguments);
} else {
//不立即执行
timeoutID = setTimeout(() => {
//改变this指向并把function的参数传给fun
fun.apply(this, arguments); //fun的this的指向要与没加防抖时一致
}, delay);
}
};
}
二.节流
单位时间内触发多次事件,只执行一次
2.应用
- 鼠标经过事件,页面缩放,scroll滚动等
3.手写节流
防抖函数的侧重点在于时间的间隔,延时delay后才能执行,而节流的重点在于对操作进行限制,一定时间只能执行一次
(1)时间戳写节流
利用当前时间-先前时间>delay
function throttle(fun, delay) {
let prev = 0; //之前的时间戳
return function () {
//获取当前时间戳
let cur = +new Date();
if (cur - prev >= delay) {
//更新时间戳
prev = cur;
//立即执行
fun.apply(this, arguments);
}
};
}
如果最后一次执行刚好在delay的时间内则不会执行(顾头不顾尾)
(2)定时器写节流
利用setTimeout延迟触发
function throttle(fun,delay){
let timeoutID
return function(){
if(!timeoutID){
timeoutID = setTimeout(()=>{
fun.apply(this,arguments)
//在执行时也要清除定时器id,不然一直存在无法再次调用
timeoutID = null
},delay)
}
}
}
这种虽然可以触发最后一次了,但是第一次触发事件可能等的很长
理想的情况是第一次触发而最后一次也触发
(3)定时器+时间戳写节流
//使用时间戳时顺便清除定时器,使用定时器时顺便更新时间戳
function throttle(fun, delay) {
let timeoutID;
let prev = 0;
return function () {
let cur = +new Date();
//时间戳部分
if (cur - prev >= delay) {
prev = cur;
fun.apply(this, arguments);
//消除定时器
if (timeoutID) {
timeoutID = null;
clearTimeout(timeoutID);
}
}
//定时器部分
else if (!timeoutID) {
timeoutId = setTimeout(() => {
//记录最新的时间
prev = +new Date();
fun.apply(this, arguments);
timeoutID = null;
}, delay);
}
};
}
其实还有一个问题,如果我们的delay=500ms而现在cur-prev=200ms明显未到执行的时间,所以会走到定时器部分,而定时器部分又要再延迟delay也就是500ms执行,真正的执行时间就变成700ms了,实际应该再等300ms就执行
解决方案 : 我们可以定义一个剩余执行时间remain,如果剩余执行时间<=0,那么就立即执行,如果不是,那么这个剩余时间应该成为接下来定时器的等待时间。
//使用时间戳时顺便清除定时器,使用定时器时顺便更新时间戳
function throttle(fun, delay) {
let timeoutID;
let prev = 0;
return function () {
//时间戳部分
let cur = +new Date();
//统计剩余时间
let remain = delay - (cur - prev);
//剩余时间<=0立即执行
if (remain <= 0) {
prev = cur;
fun.apply(this, arguments);
//消除定时器
if (timeoutID) {
timeoutID = null;
clearTimeout(timeoutID);
}
}
//定时器部分
else if (!timeoutID) {
timeoutID = setTimeout(() => {
//记录最新的时间
prev = +new Date();
fun.apply(this, arguments);
timeoutID = null;
}, remain);
}
};
}