- 函数的防抖和节流
- 防抖:在用户频繁触发的时候,只识别一次(识别第一次/识别最后一次)
- 节流: 目的是频繁触发中缩减频率。假设我们频率设置为500ms,我们频繁触发了10000ms,对于防抖,则只触发一次;对于节流,则触发20次
// 第一次点击,没有立即执行,等待500MS,看看这个时间内有没有触发第二次,有触发第二次,说明在频繁点击,不去执行我们的事情(继续看第三次和第二次间隔...);如果没有触发第二次,则认为非频繁点击,此时去触发;
/*
- debounce:实现函数的防抖(目的是频繁触发中只执行一次)
- @params
- func:需要执行的函数
- wait:检测防抖的间隔频率
- immediate:是否是立即执行(如果为true是控制第一次触发的时候就执行函数,默认FALSE是以最后一次触发为准)
- @return
- 可被调用执行的函数
*//*
防抖:我的注释
1、在指定时间内,触发第二次,清除上一次的操作(定时器),重新计时,生成新的操作;触发第三次,清除上一次的操作,重新计时,生成新的操作,以此类推。超过指定时间,则执行操作。
总之,频繁操作,即在指定时间内,操作2次及2次以上,就清空上一次的操作,重新计时,生成新的操作。
2、防抖,不是每间隔指定时间,都执行一次,而是指定一个间隔时间,在间隔时间内,有木有触发2次,只要触发2次,就是频繁的,就以第一次或最后一次为主
*/
防抖的应用场景
- 输入框中频繁的输入内容,搜索或者提交信息;
- 频繁的点击按钮,触发某个事件;
- 监听浏览器滚动事件,完成某些特定操作;
- 用户缩放浏览器的resize事件;
总之,密集的事件触发,我们只希望触发比较靠后发生的事件,就可以使用防抖函数。
节流的应用场景
- 监听页面的滚动事件;
- 鼠标移动事件;
- 用户频繁点击按钮操作;
- 游戏中的一些设计;
总之,依然是密集的事件触发,但是这次密集事件触发的过程,不会等待最后一次才进行函数调用,而是会按照一定的频率进行调用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">按钮</button>
</body>
</html>
<script>
// 【这里,debounce中的this指向window】
function debounce(func, wait = 500, immediate = false) {
let timer = null;
// anonymous、setTimeout的this都是指向btn
return function anonymous(...params) {
// 第一次触发 或 立即执行的条件:immediate为true,timer为null
let now = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
// 执行函数:注意保持this和参数的完整度,让定时器的this和anonymous保持一致
// 【this指向事件源,箭头函数没有自己的this】
!immediate ? func.call(this, ...params) : null;
}, wait);
// 用于第一次触发 或 立即执行
now ? func.call(this, ...params) : null;
};
}
// -----------------------------------------------------
/*
* 假设我们频率设置为500ms,我们频繁触发了10000ms,对于防抖,则只触发一次;对于节流,则触发20次
*
* throttle:实现函数的节流(目的是频繁触发中缩减频率)
* @params
* func:需要执行的函数
* wait:自己设定的间隔时间(频率)
* @return
* 可被调用执行的函数
*/
// 【第一次触发时,remaining是负数,previous被赋值为当前时间。】
// 【第二次触发,假设是在间隔20ms,则remaining = 500 - (新的当前时间 - 上一次触发时间) = 500 - 20 = 480,也就是定时器的等待时间remaining。】
function throttle(func, wait = 500) {
let timer = null;
let previous = 0; // 记录上一次操作时间
return function anonymous(...params) {
let now = new Date(); // 当前操作的时间
let remaining = wait - (now - previous); // 剩余时间
// remaining <= 0 与 remaining > 0 的区别:后者多了个定时器,previous不一样
if (remaining <= 0) {
// 两次间隔时间超过频率:把方法执行即可
// clearTimeout是从系统中清楚定时器,timer值不会变为null 【银行系统清理排队号】
clearTimeout(timer);
// 这是给变量timer赋值为null,就可以通过timer是否为null,判断有木有定时器 【自己把小纸条丢垃圾篓,也可以不扔,拿手里,但是就不能通过timer是否为null,判断有木有定时器了。】
timer = null;
previous = now; // 【把上一次操作时间修改为当前时间】
func.call(this, ...params);
} else if (!timer) {
// 两次间隔时间小于频率,如果没有定时器,设置定时器;有定时器了,就不用重新设置定时器,而是以上一次的计时为准
// 两次间隔时间没有超过频率,说明还没有达到触发标准,设置定时器等待即可(还差多久,就等多久) 【假设时间间隔是500ms,第20ms点击,剩余480ms,就等待480ms。】
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
// 过了remaining时间后,才去执行func,所以previous不能等于now
previous = new Date(); // 【把上一次操作时间修改为当前时间】
func.call(this, ...params);
}, remaining);
}
};
}
function func() {
console.log('OK');
}
// debounce中的this指向window,因为调用debounce,前面没有任何东西。注意,这里是调用函数,而不是定义函数,如果是定义函数,那么debounce中的this指向btn
// btn.onclick = debounce(func, 500, true);
// btn.onclick = func;
btn.onclick = throttle(func, 1000);
// -------------------------------------------
// 防止频繁点击触发:设置标识进行判断
let isClick = false;
btn.onclick = function () {
// isClick为false,则不继续往下执行
if (isClick) return;
isClick = true;
setTimeout(() => {
console.log('OK');
isClick = false;
}, 500);
};
</script>