一、函数防抖
函数防抖的目的–对于频繁触发的某个操作,我们只识别一次,那么函数防抖的要素就来了:
- 要被防抖的函数
- “频繁”的界限
- 识别的是频繁操作的第一次还是最后一次
所以可以得出防抖函数的大框架:
/*
* @params
* func:要被防抖,最后执行的函数
* wait:“频繁”的时间界限
* immediate:识别第一次,还是最后一次
* @return
* 给要最终执行的func包一层匿名函数
*/
function debounce(func, wait, immediate) {
return function anonymous() {};
}
这里包一层匿名函数,是为了形成闭包,将func, wait, immediate的值存储起来
接着,我们分析一下,防抖函数主要做的是什么呢?(以下用形参名来帮助理解)
- 在事件触发的时候,我们要等wait这么长的时间,即延时执行func
- 在wait期间,无第二次操作则立即执行func; 有第二次操作,即代表频繁操作了,那么重新等待wait时间
- immediate先默认为false,即不考虑-----频繁点击时先执行第一次点击,后面点击无效果
得出防抖函数如下:
function debounce(func, wait, immediate = false) {
let timer = null;
return function anonymous() {
clearTimeout(timer);
timer = setTimeout(() => {
func.call(this);//因为包了一层匿名函数,所以事件触发时,匿名函数的this是当前元素,而func不是,所以要将func的this也指向当前元素
}, wait);
};
}
大体上函数就是这样啦,但是别忘记还要分频繁操作以后执行第一次还是最后一次:
function debounce(func, wait, immediate) {
let timer = null;
return function anonymous() {
//immediate => 想要执行频繁点击的第一次 && !timer 即第一次未设置定时器才为true
let now = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
//每一次定时器能执行时手动将timer恢复成初始状态
timer = null;
//既然要执行第一次,那么就不要让wait时间之后再执行一次函数,这里用!immediate是因为最后一次操作的timer是不会被清除掉的
!immediate ? func.call(this) : null;
}, wait);
//执行频繁点击第一次
now ? func.call(this) : null;
};
}
!!设个默认值,不传wait和immediate的时候,也方便使用啦~
<!-- body里 -->
<button id="submit">提交</button>
//防抖函数
function debounce(func, wait = 300, immediate = false) {
let timer = null;
return function anonymous(...params) {
//immediate => 想要执行频繁点击的第一次 && !timer 即第一次未设置定时器才为true
let now = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
//每一次定时器能执行时手动将timer恢复成初始状态
timer = null;
//既然要执行第一次,那么就不要让wait时间之后再执行一次函数,这里用!immediate是因为最后一次操作的timer是不会被清除掉的
!immediate ? func.call(this,...params) : null;
}, wait);
//执行频繁点击第一次
now ? func.call(this,...params) : null;
};
}
//要执行的函数,也就是要赋给debounce里的func
function test() {
console.log("ok");
}
//给事件绑定
submit.onclick = debounce(test, 500, true);
二、函数节流
函数节流的目的–在一段频繁操作中,可以触发多次,但是按照我们设定的频率,而之前说的防抖是只执行频繁操作里的一次。那么函数节流的要素是:
- 要被节流的函数
- 频率,也就是设定多少时间触发一次
所以可以得出函数节流的大框架:
function throttle(func, wait) {
return function anonymous() { }
}
节流函数的主体思路:
- 在频繁操作中每wait这么长的时间就执行一次操作
- 需要记录上一次操作的时间
- 需要记录当前时间以计算距离下一次执行的时间(用remaining表示)
- 每两次操作间隔时间达到wait时长执行, 不到wait等待remaining这么长时间再执行
根据这个思路,来写函数吧:
function throttle(func, wait) {
let timer = null,
pervious = 0; // 用来记录上一次执行的时间,放在匿名函数的上级是为了保存这个变量的值
return function anonymous() {
let now = new Date(), //获取每次操作的当前时间
remaining = wait - (now - pervious) //获取离执行下一次操作还剩多少时间
if (remaining <= 0) {
//间隔已经达到wait时间,执行func
clearTimeout(timer); //既然要执行,那此时应该清掉现有的定时器
timer = null;
pervious = now; //获取当前操作的时间以作为下一次操作时间的上一次参照
func.call(this);
} else if (!timer) {
//间隔没有达到wait时间,且没有设置过定时器(设过等到时间触发就好了)
timer = setTimeout(() => {
timer = null;
pervious = new Date(); //每次定时器能够执行时获取当前时间
func.call(this);
}, remaining);
}
};
}
优化一下~~~
function throttle(func, wait = 300) {
let timer = null,
pervious = 0; // 用来记录上一次执行的时间,放在匿名函数的上级是为了保存这个变量的值
return function anonymous(...params) {
let now = new Date(), //获取每次操作的当前时间
remaining = wait - (now - pervious) //获取离执行下一次操作还剩多少时间
if (remaining <= 0) {
//间隔已经达到wait时间,执行func
clearTimeout(timer); //既然要执行,那此时应该清掉现有的定时器
timer = null;
pervious = now; //获取当前操作的时间以作为下一次操作时间的上一次参照
func.call(this, ...params);
} else if (!timer) {
//间隔没有达到wait时间,且没有设置过定时器(设过等到时间触发就好了)
timer = setTimeout(() => {
timer = null;
pervious = new Date(); //每次定时器能够执行时获取当前时间
func.call(this, ...params);
}, remaining);
}
};
}
三、总结
函数防抖是为了在频繁操作中只识别一次,适合点击提交表单这种;
函数节流是为了在频繁操作中只按照频率识别,适合页面滚动和输入过程中的模糊匹配等。