大家好,欢迎来到IT知识分享网。
1、什么是防抖
在某设定的时间内,没有再次触发某个函数时,才真正的调用这个函数。
过程:
- 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
- 当事件密集触发时,函数的触发会被频繁的推迟;
- 只有等待了一段时间也没有事件触发,才会真正的执行响应函数;
2、防抖的应用场景
- 输入框中频繁的输入内容,搜索或者提交信息;
- 频繁的点击按钮,触发某个事件;
- 监听浏览器滚动事件,完成某些特定操作;
- 用户缩放浏览器的resize事件;
总之,密集的事件触发,我们只希望触发比较靠后发生的事件,就可以使用防抖函数;
3、防抖函数的实现
下面我们根据需求场景,一步一步深入实现防抖函数
3.1 防抖基本功能
防抖函数的核心思路如下:
- 当触发一个函数时,并不会立即执行这个函数,而是会延迟(通过定时器来延迟函数的执行)
- 如果在延迟时间内,有重新触发函数,那么取消上一次的函数执行(取消定时器);
- 如果在延迟时间内,没有重新触发函数,那么这个函数就正常执行(执行传入的函数);
接下来,就是将思路转成代码即可:
- 定义debounce函数要求传入两个参数
- 需要处理的函数fn;
- 延迟时间;
- 通过定时器来延迟传入函数fn的执行
- 如果在此期间有再次触发这个函数,那么clearTimeout取消这个定时器;
- 如果没有触发,那么在定时器的回调函数中执行即可;
代码实现 :
//debounce.js / * @param {*} fn 要执行的函数 * @param {*} delay 延迟时间 * @returns */ function debounce(fn, delay) { // 1.定义一个定时器, 保存上一次的定时器 let timer = null // 2.真正执行的函数 const _debounce = function () { // 取消上一次的定时器 if (timer) clearTimeout(timer) // 延迟执行 timer = setTimeout(() => { // 外部传入的真正要执行的函数 fn() }, delay) } return _debounce }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`)
}
inputEl.oninput = debounce(inputChange, 1000)
</script>
</body>
</html>
3.2 绑定this和参数
我们知道在oninput事件触发时会有参数传递,并且触发的函数中this是指向当前的元素节点的
- 目前我们fn的执行是一个独立函数调用,它里面的this是window
- 我们需要将其修改为对应的节点对象,而返回的function中的this指向的是节点对象;
- 目前我们的fn在执行时是没有传递任何的参数的,它需要将触发事件时传递的参数传递给fn
- 而我们返回的function中的arguments正是我们需要的参数;
所以我们的代码可以进行如下的优化:
//debounce.js / * @param {*} fn 要执行的函数 * @param {*} delay 延迟时间 * @returns */ function debounce(fn, delay) { // 1.定义一个定时器, 保存上一次的定时器 let timer = null // 2.真正执行的函数 const _debounce = function (...args) { // 取消上一次的定时器 if (timer) clearTimeout(timer) // 延迟执行 timer = setTimeout(() => { // 外部传入的真正要执行的函数,绑定this和参数 fn.apply(this, args) }, delay) } return _debounce }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
}
inputEl.oninput = debounce(inputChange, 1000)
</script>
</body>
</html>
3.3 防抖函数第一次触发,立即执行
目前我们的事件触发都要等到delay时间,但是某些场景是用户开始输入时的第一次是立即执行的,后续的输入才需要等待,我们可以如何优化呢?
- 我们可以让用户多传入一个参数:immediate
- 那么第一次就立即执行
- 后来的事件需要等待delay时间执行
- immediate为false,或者不传,那么按照上面的防抖进行操作
- immediate为true
- 我们可以根据是否传入immediate进行不同的处理方式:
//debounce.js / * @param {*} fn 要执行的函数 * @param {*} delay 延迟时间 * @param {*} immediate 是否立即执行 * @returns */ function debounce(fn, delay, immediate = false) { // 1.定义一个定时器, 保存上一次的定时器 let timer = null; let isInvoke = false; //记录立即执行是否已执行过 // 2.真正执行的函数 const _debounce = function (...args) { // 取消上一次的定时器 if (timer) clearTimeout(timer); // 判断是否需要立即执行 if (immediate && !isInvoke) { fn.apply(this, args); isInvoke = true; } else { // 延迟执行 timer = setTimeout(() => { // 外部传入的真正要执行的函数 fn.apply(this, args); //没有这个步骤时,只有第一次输入是立即执行,即使后面延迟执行后再输入也是延迟执行; // 有这个步骤时,第一次输入时立即执行,后面延迟执行后再输入也会有立即执行 isInvoke = false timer = null }, delay); } }; return _debounce; }
代码调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
}
inputEl.oninput = debounce(inputChange, 1000, true)
</script>
</body>
</html>
3.4 当我们触发函数时,在未到执行时间,可以取消函数执行
有时候,在等待执行的过程中,可能需要取消之前的操作:
- 比如用户进行了搜索,但是还没有来得及发送搜索的情况下,退出了界面;
- 当用户退出时,之前的操作就可以取消掉;
我们这里将delay时间改长,并且在下方增加一个按钮:
- 在延迟时间内,我们点击按钮,就取消之前的函数执行:
//debounce.js / * @param {*} fn 要执行的函数 * @param {*} delay 延迟时间 * @param {*} immediate 是否立即执行 * @returns */ function debounce(fn, delay, immediate = false) { // 1.定义一个定时器, 保存上一次的定时器 let timer = null; let isInvoke = false; //记录立即执行是否已执行过 // 2.真正执行的函数 const _debounce = function (...args) { // 取消上一次的定时器 if (timer) clearTimeout(timer); // 判断是否需要立即执行 if (immediate && !isInvoke) { fn.apply(this, args); isInvoke = true; } else { // 延迟执行 timer = setTimeout(() => { // 外部传入的真正要执行的函数 fn.apply(this, args); //没有这个步骤时,只有第一次输入是立即执行,即使后面延迟执行后再输入也是延迟执行; // 有这个步骤时,第一次输入时立即执行,后面延迟执行后再输入也会有立即执行 isInvoke = false timer = null }, delay); } }; // 封装取消功能 _debounce.cancel = function () { if (timer) clearTimeout(timer) timer = null //重置 isInvoke = false //重置 } return _debounce; }
代码调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
//输入触发事件
const debounceChange = debounce(inputChange, 2000, true)
inputEl.oninput = debounceChange
// 取消功能
cancelBtn.onclick = function () {
debounceChange.cancel()
}
</script>
</body>
</html>
3.5 当触发的函数有返回值时,获取在防抖函数中执行的结果
有时候fn函数执行结束后还有返回值,如果我们希望拿到这个返回值应该怎么办呢?
先明确一个操作:
- 内部执行fn函数大多数情况是异步执行的(在setTimeout中执行)
- 所以通过return是无法拿到返回值的
异步的操作如何获取返回值呢?
- 方法一:通过回调函数
- 方法二:通过Promise的resolve
(1)给debounce函数,多添加一个参数,参数为一个回调函数:
//debounce.js / * @param {*} fn 要执行的函数 * @param {*} delay 延迟时间 * @param {*} immediate 是否立即执行 * @param {*} resultCallback 用来操作返回值的函数 * @returns */ function debounce(fn, delay, immediate = false, resultCallback) { // 1.定义一个定时器, 保存上一次的定时器 let timer = null; let isInvoke = false; //记录立即执行是否已执行过 // 2.真正执行的函数 const _debounce = function (...args) { // 取消上一次的定时器 if (timer) clearTimeout(timer); // 判断是否需要立即执行 if (immediate && !isInvoke) { const result = fn.apply(this, args) if (resultCallback) resultCallback(result) //通过函数参数来返回值 isInvoke = true; } else { // 延迟执行 timer = setTimeout(() => { // 外部传入的真正要执行的函数 const result = fn.apply(this, args) if (resultCallback) resultCallback(result) //没有这个步骤时,只有第一次输入是立即执行,即使后面延迟执行后再输入也是延迟执行; // 有这个步骤时,第一次输入时立即执行,后面延迟执行后再输入也会有立即执行 isInvoke = false timer = null }, delay); } }; // 封装取消功能 _debounce.cancel = function () { if (timer) clearTimeout(timer) timer = null //重置 isInvoke = false //重置 } return _debounce; }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
// 返回值
return "aaaaaaaaaaaa"
}
// 方法一 通过添加一个函数参数来获取
const debounceChange = debounce(inputChange, 1000, true, (res) => {
console.log("函数参数返回的值:", res)
})
inputEl.oninput = debounceChange
// 取消功能
cancelBtn.onclick = function () {
debounceChange.cancel()
}
</script>
</body>
</html>
(2)使用Promise来返回执行结果:
/ * @param {*} fn 要执行的函数 * @param {*} delay 延迟时间 * @param {*} immediate 是否立即执行 * @returns */ function debounce(fn, delay, immediate = false) { // 1.定义一个定时器, 保存上一次的定时器 let timer = null let isInvoke = false // 2.真正执行的函数 const _debounce = function (...args) { return new Promise((resolve, reject) => { //通过promise来返回值 // 取消上一次的定时器 if (timer) clearTimeout(timer) // 判断是否需要立即执行 if (immediate && !isInvoke) { const result = fn.apply(this, args) resolve(result) isInvoke = true } else { // 延迟执行 timer = setTimeout(() => { // 外部传入的真正要执行的函数 const result = fn.apply(this, args) resolve(result) isInvoke = false timer = null }, delay) } }) } // 封装取消功能 _debounce.cancel = function () { if (timer) clearTimeout(timer) timer = null isInvoke = false } return _debounce }
代码调用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text">
<button id="cancel">取消</button>
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
let counter = 0
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event)
// 返回值
return "aaaaaaaaaaaa"
}
// 方法二 返回一个promise
const debounceChange = debounce(inputChange, 3000, false)//相当于_debounce
const tempCallback = function (...args) {
debounceChange.apply(this, args).then(res => {//此时this绑定的是input对象
console.log("Promise的返回值结果:", res)
})
}
inputEl.oninput = tempCallback
// 取消功能
cancelBtn.onclick = function () {
debounceChange.cancel()
}
</script>
</body>
</html>
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/111415.html