还在写普通防抖?
不会防抖?没有关系,我们先从普通防抖开始
一:防抖意义及其工作原理
防抖是一种函数调用的优化策略,主要用于避免在短时间内连续多次触发某个函数,从而减轻系统负担。在面试中,也是常考的前端性能优化策略,防抖的主要思想是在一系列连续的事件触发时,只在一段时间之后执行一次处理函数。如果在这段时间内又有新的事件触发,则会重新计时。
- 初始化: 用户触发事件(如按钮点击、输入框输入等)。
- 设置定时器: 第一次触发事件时,设置一个定时器,计划在一定时间(比如1秒)后执行处理函数。
- 重复触发: 如果在定时器到期之前又触发了事件,则清除之前的定时器,并重新设置一个新的定时器。
- 最终执行: 只有当定时器到期而没有新的事件触发时,才会执行处理函数
二:普通防抖
下面根据防抖的核心,来一个最朴素的防抖
三:优化普通防抖
原因很简单,当定时器到期时,fn
(即 handle
)会被调用。在定时器的回调函数中,如果没有明确绑定 this
的值,那么在非严格模式下,this
会默认指向全局对象(通常是 window
),那么我们现在要怎么做才能正确的将handle
中的this
指回btn
呢?
-
找出能够指向
btn
的函数:匿名函数(return function(e) { ... }
)是在debounce
函数内部定义的,它是在按钮点击事件触发时执行的,由于它是作为事件监听器的一部分被执行的,因此它的this
值会自动指向触发事件的元素,即btn
。 -
利用显示绑定强行掰弯
handle
里的this
,使其指向btn
:-
在定时器的回调函数中直接使用
call
或apply
方法来调用handle
函数,并将this
显式地绑定为btn
元素。 -
使用箭头函数来简化代码,并确保
this
始终指向正确的元素。
此外,我们再加上事件参数,以下是两份完整的优化后代码:
-
使用 call
或 apply
方法:
let btn = document.getElementById('btn');
function handle(e) {
// AJAX 请求
console.log('提交', this);
}
// 防抖函数
function debounce(fn) {
let timer = null;
return function(e) {
const that = this//匿名函数的this执行btn
// 如果第二次时间没到1秒,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(function() {
fn.call(that, e); // 使用 `call` 显式绑定到匿名函数的 `this` 值
}, 1000);
};
}
// 添加点击事件监听器
btn.addEventListener('click', debounce(handle));
使用箭头函数:
let btn = document.getElementById('btn');
function handle(e) {
// AJAX 请求
console.log('提交', this);
}
// 防抖函数
function debounce(fn) {
let timer = null;
return function(e) {
// 如果第二次时间没到1秒,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(() => {
fn(e); // 箭头函数会继承外部作用域的 `this` 值
}, 1000);
};
}
// 添加点击事件监听器
btn.addEventListener('click', debounce(handle));
四:高级防抖
聊了这么久基础,终于进入难点了,简单的防抖还不足以打动面试官,我们要学习更高级,性能更好的防抖。
手写高级防抖
-
定义防抖函数
debounce
:-
function debounce(func, wait, immediate) { ... }
: 这个函数接受三个参数:func
: 要防抖的函数。wait
: 等待的时间(毫秒)。immediate
: 布尔值,表示是否立即执行函数。
-
-
内部变量:
-
var timeout, result;
: 定义了两个变量:timeout
: 用于保存定时器的引用。result
: 用于保存函数func
的执行结果。
-
-
返回新的函数:
-
return function() { ... }
: 返回一个新的匿名函数,这个匿名函数将在每次调用时被调用。-
var context = this;
: 保存当前函数的this
值。 -
var args = arguments;
: 保存传递给当前函数的所有参数。 -
if (timeout) clearTimeout(timeout);
: 如果存在定时器,则清除它。 -
if (immediate) { ... } else { ... }
: 根据immediate
的值决定执行逻辑。- 如果
immediate
为true
,则尝试立即执行func
,并在wait
时间后清除定时器。 - 如果
immediate
为false
,则在wait
时间后执行func
。
- 如果
-
-
-
使用防抖函数:
debounce(getUserAction, 1000, true);
: 调用防抖函数,传入getUserAction
函数作为处理函数,设置等待时间为 1000 毫秒,并立即执行处理函数
function debounce(func, wait, immediate) {
var timeout, result; // 自由变量空间
// 真正要执行的函数
return function() { // 二传手
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout); // 清除定时器
if (immediate) {
var callNow = !timeout;//立即执行一次
timeout = setTimeout(function() {
timeout = null; // 释放
}, wait);
if (callNow) result = func.apply(context, args);
} else {
timeout = setTimeout(function() {
result = func.apply(context, args);
}, wait);
}
return result;
};
}
// 使用防抖函数
debounce(getUserAction, 1000, true);
与普通防抖的不同
-
立即执行模式:
- 新版本的防抖函数支持立即执行模式,即如果
immediate
为true
,则在首次触发事件时立即执行func
,并在wait
时间后清除定时器。这在某些场景下非常有用,例如当需要立即响应用户的第一次动作,但后续动作需要防抖处理时。
- 新版本的防抖函数支持立即执行模式,即如果
-
更灵活的参数:
- 新版本的防抖函数接受第三个参数
immediate
,使得开发者可以根据具体需求选择是否立即执行处理函数。
- 新版本的防抖函数接受第三个参数
-
更完整的实现:
- 新版本的防抖函数不仅返回处理函数的结果,而且在返回前会清除定时器,确保内存资源得到释放。
为什么更优秀
-
灵活性:
- 支持立即执行和延迟执行两种模式,提供了更多的选择性。
-
资源管理:
- 在立即执行模式下,通过设置定时器来清除
timeout
变量,确保了内存资源的有效管理。
- 在立即执行模式下,通过设置定时器来清除
-
用户体验:
- 立即执行模式可以提供更好的用户体验,因为它可以在第一次触发事件时立即响应,之后再进行防抖处理。
转载自:https://juejin.cn/post/7398462127441428489