防抖节流
总结:
平时键盘抬起就会搜索,防抖后,间隔一段时间不输入才会搜索,节流后,在频繁触发的事件流中,函数以固定的频率执行。
1、 函数防抖
类似法师技能读条,还没读完条再按技能,会重新读条
-
防抖的原理就是:触发事件,但是我一定 在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就 以新的事件的时间为准,n 秒后才执行,总之,就是要等你 触发完事件 n 秒内不再触发事件,我才执行。
-
实现防抖的 典型逻辑 是通过设置一个定时器,每次事件触发时清除之前的定时器,然后重新设置一个新的定时器。只有当最后一次触发后经过了设定的延迟时间,定时器中的函数才会执行。
function debounce(fn, delay) { let timeId = null; return function (...args) { // 清理定时器,才能保证不停读条-覆盖上一次 timeId && clearTimeout(timeId); timeId = setTimeout(() => { fn.apply(this, ...args); }, delay) } }
-
常见错误:
1、记得清理定时器,才能够做到【间隔一段时间不输入才会执行回调
2、函数节流
就是 一直按着技能键,也能在规定时间内发技能
-
节流通常是通过一个 标记变量和定时器 来实现,当函数执行后,设置一个定时器,在定时器未结束期间(即未达到规定的时间间隔),再次触发事件时函数不会执行,直到定时器结束,标记变量重置,函数才可以再次执行。
-
常见错误:
1、不用再清理定时器来处理了,也就不用记录
2、setTimeout用来管理 flag的值,即可控制
3、第一次是会触发的
// 不是使用计算时间的方式来判断,而是直接使用 flag标记变量 function throttle(fn, delay) { let flag = true; return function (...args) { if (!flag) { return; } fn.apply(this, args); flag = false; setTimeout(() => { flag = true; }, delay); }; }
3、实际应用场景
-
防抖
debounce
-
search
搜索联想,用户在不断输入值时,用防抖来节约请求资源 -
浏览器
resize
的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
-
-
节流
throttle
- 鼠标不断点击触发,
mousedown
(单位时间内只触发一次) - 监听滚动事件,比如是否滑到底部自动加载更多,用
throttle
来判断
- 鼠标不断点击触发,
函数防抖(debounce) - 节约开销
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
案例:一段键盘输入就执行函数的代码 - 如图,存在抖动问题
将函数进行防抖处理 - 优化后:
// 通过防抖函数处理后,只有当你在指定间隔内(1s)没有输入时,才会执行函数
function debounce(fn, delay) {
let timeId = null;
return function (...args) {
if(timeId) {
clearTimeout(timeId);
}
timeId = setTimeout(() => {
fn(...args);
}, delay)
}
}
// 添加防抖处理
let keyUp = (value) => console.log(value);
let debounceKeyUp = debounce(keyUp, 1000);
// 给指定 input 添加监听函数
let input = document.getElementById('input');
input.addEventListener('keyup', (e) => {
debounceKeyUp(e.target.value);
})
补充:这里有一个很有趣的例子 - setInterval
里面包裹 setTimeout
,讨论下时间
-
如果外部的
delay
时间大于 防抖的delay
时间 - 函数最终以外部时间周期执行:let show = function () {console.log(1);} // 第一次 3秒,之后每次都是 2秒 执行一次 setInterval(debounce(show, 1000), 2000); // 第一次 4秒,之后每次都是 3秒 执行一次 setInterval(debounce(show, 1000), 3000);
原因分析如图:
-
如果外部的
delay
时间小于 防抖的delay
时间 -show
函数不会执行:setInterval(debounce(show, 4000), 3000);
原因分析如图:
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
节流
// 在不断输入时,ajax会按照我们设定的时间,每1s执行一次
let throttle = (callback, delay) => {
let last;
return (arguments) => {
let now = +new Date();
if(last && now < last + delay) {
clearTimeout(callback.timeId);
callback.timeId = setTimeout(() => {
last = now;
callback.call(this, arguments)
}, delay)
} else {
// 如果现在 = 上一次的时间 + 延迟时间,那么就马上执行函数
last = now;
callback.call(this, arguments)
}
}
}
// 可以简化成 flag 来实现
function throttle(fn, delay) {
let timer = null;
let flag = true;
return function (...args) {
if (!flag) {
return;
}
fn.apply(this, args);
flag = false;
timer = setTimeout(() => {
flag = true;
}, delay);
};
}
let throttleAjax = throttle(ajax, 1000);
let input3 = document.getElementById('input3')
input3.addEventListener('keyup', (e) => {
throttleAjax(e.target.value);
});
解决上文外部的 delay
时间小于 防抖的 delay
时间 - show
函数不会执行
// 不管我们设定的执行时间间隔多小,总是1s内只执行一次。
setInterval(throttle(show,3000),2000);
看一下打印结果:
分析 - 利用定时执行函数里面 now
的闭包,将 last 时间设置成定时任务开始的时间: