一、防抖的概念和使用库实现防抖
1.1. 认识防抖
-
防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时
-
防抖:在事件触发时,相应的回调函数并不会立即执行,而是会等待n秒。当事件频繁触发时,函数的触发也会被频繁推迟。只有等待了n秒后,事件都没有被触发,对应的回调函数才会被执行
const inputEl = document.querySelector("input") let count = 0 // 为了实现能像淘宝那样,用户输入一个词,就有相应的联想词供用户选择 // 我监听了这个输入框的输入,当用户在输入框每输入一次新的词,监听input输入的这个回调函数就会发送一次网络请求 // 但是这样做的弊端就是,如果用户明确的知道自己想要什么东西,并且快速输入了商品名称时,这个回调函数依然会被多次调用。但其实这种情况就不应该再针对每个词,都让监听input的回调函数发送网络请求去做联想了,因为这样会使服务器的压力很大 // 我们应该实现的功能/正确做法应该是:当用户输入一个词停顿了n秒之后,就请求数据做联想,但是如果用户快速输入了一个商品名称时,那么前面的“那几个词”就不应该对它们做联想 // 这时我们就可以使用“防抖”:意思就是当监听到有一个词被输入时,对监听的这个操作的回调函数延迟n秒执行,但是如果在n秒内又监听到新的词被输入了,那么就取消对上次回调函数的执行,转而继续对监听到的输入的新的词调起的回调函数做延迟n秒执行的操作,如此循环。 inputEl.oninput = function() { console.log(`发送网络请求${count++}`, this.value) }
1.2. underscore实现防抖
-
underscore是JavaScript的库,里面实现了很多函数,其中就有实现防抖功能的函数
-
利用underscore实现防抖的具体步骤
-
通过CDN引入网络的文件
-
调用其中的debounce函数
_.debounce(fn, 400)
<input type="text"> <!-- 通过CDN引入网络的文件 --> <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.6/underscore-umd-min.js"></script> <script> const inputEl = document.querySelector("input") let count = 0 // 这就实现防抖了 inputEl.oninput = _.debounce(function() { console.log(`发送网络请求${count++}`, this.value) }, 400) </script>
-
二、手写防抖实现
2.1. 基本实现
<body>
<input type="text">
<script>
function zdDebounce(fn, delay) {
// 3.定义一个变量用来接收setTimeout的Id
let timer = null
// 1.这个函数将会被绑定在事件上,如果事件被频繁触发,那么绑定在事件上的_debounce函数也会被频繁触发
const _debounce = function() {
// 4.如果第二次事件被触发的时候,第一次事件触发的回调函数没执行,那就使第一次事件触发的回调函数取消执行,转而对第二次事件触发的回调函数重新进行计时
if (timer) clearTimeout(timer)
// 2.利用setTimeout对事件触发的回调函数的执行延迟
timer = setTimeout(() => {
fn()
timer = null
}, delay)
}
return _debounce
}
</script>
<script>
const inputEl = document.querySelector("input")
let count = 0
inputEl.oninput = zdDebounce(function() {
console.log(`发送网络请求${count++}`, this.value)
}, 2000)
</script>
</body>
2.2. this和参数的绑定
<body>
<input type="text">
<script>
// 2.this和参数的绑定
function zdDebounce(fn, delay) {
let timer = null
function _debounce(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
return _debounce
}
</script>
<script>
const inputEl = document.querySelector("input")
let count = 0
inputEl.oninput = zdDebounce(function(event) {
console.log(`发送网络请求${count++}`, this.value, event)
}, 2000)
</script>
</body>
2.3. 立即执行:事件第一次被触发就执行一次回调函数
<body>
<input type="text">
<script>
// 3.立即执行:事件第一次触发就执行一次相应的回调函数
function zdDebounce(fn, delay, immediate = false) {
let timer = null
let isFlag = true
function _debounce(...args) {
if (timer) clearTimeout(timer)
if (immediate && isFlag) {
fn.apply(this, args)
isFlag = false
return
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
isFlag = true
}, delay)
}
return _debounce
}
</script>
<script>
const inputEl = document.querySelector("input")
let count = 0
inputEl.oninput = zdDebounce(function(event) {
console.log(`发送网络请求${count++}`, this.value, event)
}, 2000, true)
</script>
</body>
2.4. 取消最后一次执行的功能
<body>
<input type="text">
<button class="cancel">取消</button>
<script>
// 4.添加取消执行的功能:要取消执行,就是拿到timer,点击了取消按钮时,清除timer就好了
function zdDebounce(fn, delay, immediate = false) {
let timer = null
let isFlag = true
function _debounce(...args) {
if (timer) clearTimeout(timer)
if (immediate && isFlag) {
fn.apply(this, args)
isFlag = false
return
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
isFlag = true
}, delay)
}
// 函数也是一个对象,所以可以给它添加一个cancel方法
_debounce.cancel = function() {
clearTimeout(timer)
}
return _debounce
}
</script>
<script>
const inputEl = document.querySelector("input")
const cancelEl = document.querySelector(".cancel")
let count = 0
// 这个返回的函数(对象)中是有cancel这个方法的
const returnValue = zdDebounce(function(event) {
console.log(`发送网络请求${count++}`, this.value, event)
}, 2000, true)
inputEl.oninput = returnValue
cancelEl.onclick = function() {
returnValue.cancel()
}
</script>
</body>