防抖
当你一直触发click、input、resize等事件的时候,对应的函数不会立即调用执行,等你停下来的时候,并等待3秒(假如是3秒,具体等待时间可以作为参数传入)然后才执行对应的函数,假如3秒内你又触发,那继续等3秒
总而言之,言而总之,只有在某个时间内,没有再次触发这个事件时,才真正的调用相应的函数
应用场景:
- 输入框输入搜索内容
- 频繁点击某个按钮(比如你要点一个按钮删除某条数据,后台需要一定时间处理,你一直点,一直调删除接口,可能就会接口报错)
- 缩放浏览器的resize事件
- 监听浏览器scroll滚动事件,完成某些特定操作;
下面上代码:
基础防抖
不加防抖是这样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="text" />
<script>
const inputEl = document.querySelector("input");
inputEl.oninput = function () {
console.log("数你触发几次,看我打印多少次");
};
</script>
</body>
</html>
实现一个基本的防抖
//debounce.js
function customeDebounce(fn, delay) {
//记录一个定时器
let timer = null
const debounceFn = () => {
//如果在delay时间内再次触发,就取消上一次的timer,否则会触发多次,因为
//setTimeout会在delay时间到了把它的第一个回调参数放到宏任务对列,js线程会依次执行宏任务队列里面的这些回调函数
//所以在delay时间内触发多次,每次就要在delay时间到之前取消上一次的timer,只留下最后一个
if (timer) clearTimeout(timer)
//重新计时,赋值一个新的timer
timer = setTimeout(() => {
fn()
//timer变量存在闭包内,要手动销毁它
timer = null
}, delay)
}
//返回给调用者真正执行的函数
return debounceFn
}
// debounce.html
<body>
<input type="text" />
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input");
inputEl.oninput = customeDebounce(function () {
console.log("数你触发几次,看我打印多少次");
}, 2000);
</script>
</body>
加入参数
function customeDebounce(fn, delay) {
let timer = null
const debounceFn = function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
return debounceFn
}
本来fn是调用者直接触发的函数,现在把debounceFn返给调用者,那么就要让fn的this和debounceFn保持一致,那么debounceFn就不能再用箭头函数了,箭头函数不绑定this,由上层作用域决定,所以setTimeout中回调函数的this由上层作用域决定,就是debounceFn的this.
关于this绑定问题,可以查看 搞清楚this的几种绑定规则,将this指向一网打尽
<body>
<input type="text" />
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input");
inputEl.oninput = customeDebounce(function (e) {
console.log("数你触发几次,看我打印多少次");
console.log(this,e);
}, 2000);
</script>
</body>
取消功能
function customeDebounce(fn, delay) {
let timer = null
const debounceFn = function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
//函数作为对象,可以添加一个函数属性
debounceFn.cancelDebounce = () => {
//如果定时器还存在,就取消它
timer && clearTimeout(timer)
}
return debounceFn
}
<body>
<input type="text" />
<button>取消</button>
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input");
const btnEl = document.querySelector("button");
inputEl.addEventListener("input", function () {
console.log("没加防抖,看我打印几次");
});
const debounceFn = customeDebounce(function (e) {
console.log("加了防抖,看我打印几次");
console.log(this, e);
}, 1000);
inputEl.addEventListener("input", debounceFn);
btnEl.onclick = function () {
console.log("点取消");
debounceFn.cancelDebounce();
};
</script>
</body>
立即执行
function customeDebounce(fn, delay, immediate = false) {
let timer = null
//定义一个变量控制第一次执行
let isExecuted = false
const debounceFn = function (...args) {
if (timer) clearTimeout(timer)
//第一次触发,进入条件内
if (immediate && !isExecuted) {
//立即执行一次
fn.apply(this, args)
//执行完改为true,意味着在delay时间内多次触发,只有第一次触发进入if条件,后面的用定时器延迟
isExecuted = true
//执行完直接返回,不需要setTimeout把第一次触发再延迟执行一次,后面的触发照旧进入setTimeout
return
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
//执行完还原为false,delay时间过后再触发事件,上面的if立即执行重新生效
isExecuted = false
}, delay)
}
debounceFn.cancelDebounce = () => {
timer && clearTimeout(timer)
timer = null
//还原false,既然取消那下一次触发就该回复if条件立即执行
isExecuted = false
}
return debounceFn
}
<body>
<input type="text" />
<button>取消</button>
<script src="./debounce.js"></script>
<script>
let counter = 1;
const inputEl = document.querySelector("input");
const btnEl = document.querySelector("button");
inputEl.addEventListener("input", function () {
console.log(`没加防抖,看我执行${counter++}次`);
});
const debounceFn = customeDebounce(
function () {
counter--;
console.log(`加了防抖,看我在第${counter++}次执行`);
},
1000,
true
);
inputEl.addEventListener("input", debounceFn);
btnEl.onclick = function () {
console.log("点取消");
debounceFn.cancelDebounce();
};
</script>
</body>
返回值
function customeDebounce(fn, delay, immediate = false, callBack) {
let timer = null
let isExecuted = false
const debounceFn = function (...args) {
return new Promise((resolve, reject) => {
try {
if (timer) clearTimeout(timer)
let res = undefined
if (immediate && !isExecuted) {
res = fn.apply(this, args)
callBack && callBack(res)
resolve(res)
isExecuted = true
return
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
callBack && callBack(res)
resolve(res)
isExecuted = false
}, delay)
} catch (error) {
reject(error)
}
})
}
debounceFn.cancelDebounce = () => {
timer && clearTimeout(timer)
timer = null
isExecuted = false
}
return debounceFn
}
setTimeout中是延迟执行,不能通过同步的方式来获取fn的返回值,可以通过两种方式来获取返回值,一是通过回调函数的方式在fn执行完后,把结果放到回调函数的参数中,另一种就是返回promise,调用者可以任意通过一种方式获取返回值
<body>
<input type="text" />
<button>取消</button>
<script src="./debounce.js"></script>
<script>
const debounceFn = customeDebounce(
function () {
return "我是结果,看你怎么拿我";
},
1000,
true,
function (res) {
console.log(res); //我是结果,看你怎么拿我
}
);
debounceFn().then((res) => {
console.log(res); //我是结果,看你怎么拿我
});
</script>
</body>
如果调用者参数传错,也可以捕获错误
<body>
<input type="text" />
<button>取消</button>
<script src="./debounce.js"></script>
<script>
const debounceFn = customeDebounce(
function () {
return "我是结果,看你怎么拿我";
},
1000,
true,
"传的不是函数"
);
debounceFn()
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err); //TypeError: callBack is not a function
});
</script>
</body>