关于手写防抖函数中的this、闭包和默认参数
前言
什么是防抖?
在JavaScript中,防抖(debounce)是一种技术,用于限制连续触发的事件处理函数的执行频率,以减少性能问题或不必要的资源消耗。当事件被触发时,防抖函数会等待一段时间(称为延迟时间),如果在这段时间内没有再次触发该事件,那么才会执行事件处理函数。如果在延迟时间内事件再次被触发,延迟时间会被重新计时。 (在规定的时间内没有下一次的触发,就执行)
防抖的好处
- 减少性能消耗:防抖可以减少事件处理函数的执行次数,尤其是针对频繁触发的事件,如窗口大小调整、滚动事件等。通过延迟执行事件处理函数,可以有效减少不必要的计算和资源消耗,从而提升页面性能。
- 优化用户体验:对于一些需要用户输入或操作的交互场景,如搜索框输入、按钮点击等,防抖可以避免因连续触发事件而导致的不必要的 UI 变化或操作响应,提升用户体验。
- 控制请求发送频率:在涉及网络请求的场景中,如搜索联想、自动保存等,防抖可以控制请求的发送频率,避免过多的请求发送到服务器,减轻服务器压力,提升系统稳定性和性能。
- 解决抖动问题:防抖可以解决由于硬件问题或触发条件不明确而导致的事件抖动问题。例如,当用户快速点击按钮时,防抖可以确保只有最后一次点击被响应,避免因多次点击而导致的不一致或意外行为。
防抖用于哪?
防抖通常用于处理频繁触发的事件,比如窗口大小调整、滚动事件、搜索框输入等。通过使用防抖技术,可以有效地减少事件处理函数的执行次数,从而提升性能和用户体验。
正文
来实现定义一个button按钮,为该按钮绑定一个监听事件,当点击该按钮的时候,就触发回调函数,实现点击按钮,就会触发回调函数,在控制台输出'提交',代码实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>debounce</title>
</head>
<body>
<button id="btn">提交</button>
<script>
let btn = document.getElementById('btn');
function handle() {
// ajax请求
console.log('提交');
}
btn.addEventListener('click', handle);
</script>
</body>
</html>
如果一直点击按钮,控制台就会一直输出,我们设置一个定时器,每隔1s再触发回调函数
// 防抖函数
function debounce(fn) {
return function() {
setTimeout(()=>{
fn();
}, 1000);
}
}
然后将btn的绑定事件监听代码改为:
btn.addEventListener('click', debounce(handle));
关于形成的闭包
如果第二次的时间没到1s,就销毁上一次的定时器,为实现该效果设置一个变量timer,初始值设置为null,每次触发点击事件的回调函数,先销毁上一次的定时器,再将新的定时器赋值给timer。
function debounce(fn) {
let timer = null;
return function() {
// 如果第二次的时间没到1s,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(()=>{
fn();
}, 1000);
}
}
防抖函数debounce执行完会形成一个闭包,里面包含子函数执行要引用的变量timer,当子函数执行时,会先去自己的执行上下文的词法环境找,再去自己的变量环境找,再去debounce留下的闭包找,直到找到timer。
关于this的指向
原来的this指向btn:handle函数是由btn来绑定事件监听后点击事件触发调用的,所以为this中的隐式绑定,所以this指向btn。
<script>
let btn = document.getElementById('btn');
function handle() {
// ajax请求
console.log('提交', this);
}
btn.addEventListener('click', handle);// this 指向btn
// 防抖函数
function debounce(fn) {
let timer = null;
return function() {
// 如果第二次的时间没到1s,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(function() {
fn();
}, 1000);
}
}
</script>
设置防抖函数后this指向window:handle函数的调用是在定时器内的回调函数中调用的,定时器内的回调函数指向window,所以this指向window。
<script>
let btn = document.getElementById('btn');
function handle() {
// ajax请求
console.log('提交', this);
}
btn.addEventListener('click', debounce(handle));// this 指向window
// 防抖函数
function debounce(fn) {
let timer = null;
return function() {
// 如果第二次的时间没到1s,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(function(){
fn();
}, 1000);
}
}
</script>
那么该如何解决防抖函数中的this错误指向的问题呢?
// 防抖函数
function debounce(fn) {
let timer = null;
return function(e) {
// 如果第二次的时间没到1s,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(()=>{
fn.call(this);
}, 1000);
}
}
关于默认参数
原来的函数的默认参数e为:
<script>
let btn = document.getElementById('btn');
function handle(e) {
// ajax请求
console.log('提交', this, e);
}
btn.addEventListener('click', handle);// this 指向btn
// 防抖函数
function debounce(fn) {
let timer = null;
return function(e) {
// 如果第二次的时间没到1s,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(()=>{
fn.call(this, e);
}, 1000);
}
}
</script>
设置防抖函数后的e为undefined:
<script>
let btn = document.getElementById('btn');
function handle(e) {
// ajax请求
console.log('提交', this, e);
}
btn.addEventListener('click', debounce(handle));// this 指向window
// 防抖函数
function debounce(fn) {
let timer = null;
return function() {
// 如果第二次的时间没到1s,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(()=>{
fn.call(this);
}, 1000);
}
}
</script>
那么怎么解决这个默认参数的问题呢?
通过call将默认参数e传进去
// 防抖函数
function debounce(fn) {
let timer = null;
return function(e) {
// 如果第二次的时间没到1s,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(()=>{
fn.call(this, e);
}, 1000);
}
}
防抖函数还可以通过设定一个变量that来实现同样的效果
// 防抖函数
function debounce(fn) {
let timer = null;
return function(e) {
const that = this;
// 如果第二次的时间没到1s,就销毁上一次的定时器
clearTimeout(timer);
timer = setTimeout(function() {
fn.call(that, e);
}, 1000);
}
}
结语
快去手写一个防抖函数试试吧~
转载自:https://juejin.cn/post/7367577126857097250