一文搞懂手写防抖函数以及优化方案
20230724 手写防抖函数-百度前端一面
1.防抖函数
要求:点击一个 button,点击的时候在一定的时间内避免重复触发 ——> 防抖
初代版本:期限过了以后再执行,如果一直点就一直不执行
1.第一个版本:行内绑定 js 函数的方式(不能用闭包)
<body>
<div>
<!-- <button onclick="fun">点我</button> -->
<!--这样不行的,因为html行内绑定的函数是在具体触发的时候才执行,因为不是js,所以当然不会立即执行,而react本质是js,所以可以直接绑定不用加(),在js里面用addEventListener也和react一样不用加()-->
<button onclick="fun()">点我</button>
</div>
<script>
let timer = null;
const fun = () => {
clearTimeout(timer); //先清除定时器
timer = setTimeout(() => {
//再重新开一个
console.log(111);
}, 1000);
};
</script>
</body>
2.第二个版本:js 内监听事件绑定函数
<body>
<div>
<button id="debounceButton">点我</button>
<!-- <button onclick="fun()">点我</button> -->
</div>
<script>
let timer = null;
const fun = () => {
clearTimeout(timer);
timer = setTimeout(() => {
console.log(111);
}, 1000);
};
let button = document.getElementById("debounceButton");
// button.addEventListener("click", fun()) //错误,会立即执行
button.addEventListener("click", fun); //不用加()
</script>
</body>
改写为闭包形式:
<body>
<div>
<button id="debounceButton">点我</button>
<!-- <button onclick="fun()">点我</button> -->
</div>
<script>
const fun = () => {
let timer = null;
return () => {
clearTimeout(timer);
timer = setTimeout(() => {
console.log(111);
}, 1000);
};
};
let button = document.getElementById("debounceButton");
button.addEventListener("click", fun()); //要加(),因为是闭包函数(高阶函数)
</script>
</body>
优化后版本:立即执行,期限内不再执行(如果一直点就一直不再执行) ——> 符合实际的用户场景需求
<body>
<div>
<button id="debounceButton">点我</button>
</div>
<script>
//闭包写法:传入一个处理函数 ——> 闭包只能用debounceButton.addEventListener这个写法
function debounce(func, delay) {
let timeoutId; //相当于null
return function (...args) {
console.log(timeoutId); //定时器id号
clearTimeout(timeoutId); //只是会清除定时器的计时,但是不会把timeoutId设置为空!
console.log(timeoutId); //还是定时器id号不变
// 在防抖函数中,第一次点击会立即执行一次处理函数
if (!timeoutId) {
func.apply(this, args); //func.apply(this, args) 是 JavaScript 中的一种调用函数的方式,它的含义是调用函数 func 并传入指定的 this 上下文和参数数组 args
}
timeoutId = setTimeout(() => {
timeoutId = null; // 防抖时间到,重置 timeoutId,可以再次立即执行(这里是用timeoutId来做这个标志变量,没有timeoutId了才可以执行处理函数,也可以用flag来做这个事情)
}, delay);
};
}
function handleClick() {
console.log("按钮点击事件触发!");
// 在这里添加你想要执行的代码(实际上应该是ajax请求方法)
}
const debounceButton = document.getElementById("debounceButton");
// 添加按钮点击事件监听器,先立即执行一次处理函数,后续点击进行防抖处理
debounceButton.addEventListener("click", debounce(handleClick, 1000));
//这里js的绑定和html内的绑定不一样,这里绑定会立即执行,而html内是触发事件之后再执行
</script>
</body>
那么节流函数怎么写?
<body>
<button id="myButton">Click Me</button>
<script>
function throttle(func, delay) {
let lastTime = 0;
return function (...args) {
const currentTime = new Date().getTime(); //获取现在事件
if (currentTime - lastTime >= delay) {
//如果现在时间与最后一次执行时间之差超过了期限,就可以再次执行!
func.apply(this, args);
lastTime = currentTime; //记录最后执行时间
}
};
}
// 点击事件处理函数
function onClick() {
console.log("Button clicked!");
}
// 获取按钮元素
const button = document.getElementById("myButton");
// 绑定点击事件
button.addEventListener("click", throttle(onClick, 1000));
</script>
</body>
注意,这里不像防抖一样需要优化为一上来就执行一次,因为节流函数一定会一上来就执行一次,因为最开始的 lastTime 为 0!
防抖和节流的区别:
其实防抖就是设置一个期限,期限内只能触发一次,如果重复点击就会重开期限!也就是说期限内不能再点击,点击了就不能再次触发了! **——> 一直连点不能多次触发,**只能触发一次
而节流是设置一个期限,也是期限内只能触发一次,但是重复点击不会重开期限,期限过了之后就可与再次触发!——> 一直连点可以多次触发,只要过了期限即可,就像把水流一股一股地放出来
官方说法:对于节流(Throttling),与防抖(Debouncing)相似,它也是控制函数触发频率的一种方法。不同之处在于,节流是确保函数在一定时间间隔内最多执行一次,而防抖是在函数连续触发后的一段时间内只执行一次。
使用场景
防抖的主要特点是在一定时间内多次触发函数时,只有最后一次触发会被执行,之前的触发都会被取消,从而实现延迟执行函数的效果。防抖通常是在用户操作的最后一次触发时执行函数,适用于例如搜索建议、输入框输入后提交等场景。(初代防抖)
优化的防抖适合于:点击按钮请求 ajax 数据的情况,避免用户一直点击造成一直请求!
防抖通常用于限制用户不正常行为 或者 错误连点操作 或者 为了最后统一执行
节流的主要特点是确保函数在一定时间间隔内最多执行一次。在节流函数中,即使在指定的时间间隔内多次触发函数,函数只会在每个时间间隔内的第一次触发时执行一次,后续的触发会被忽略。节流通常用于限制事件处理函数的执行频率,防止事件频繁触发,适用于例如滚动事件、resize 事件等场景。
2.节流函数
节流函数用于限制函数的执行频率,确保函数在一定时间间隔内只能执行一次。
let flag = false;
const test2 = () => {
if (!flag) {
flag = true;
setTimeout(() => {
console.log("执行!");
flag = false;
}, 1000);
}
};
如果是封装的呢?
const test2 = (fun, time) => {
let flag = false;
return () => {
if (!flag) {
flag = true;
setTimeout(() => {
fun(this, arguments);
flag = false;
}, time);
}
};
};