认识防抖debounce函数
- 我们来理解一下它的过程:
- 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间
- 当事件密集触发时,函数的触发会被频繁的推迟
- 只有等待了一段时间也没有事件触发,才会真正的执行响应函数
- 防抖的应用场景很多:
- 输入框中频繁的输入内容,搜索或者提交信息;
- 频繁的点击按钮,触发某个事件
- 监听浏览器滚动事件,完成某些特定操作
- 用户缩放浏览器的resize事件
防抖函数的案例
-
举一个生活中常见的栗子
-
就比如玩
王者荣耀或者LOL
时的回城功能,如果点击了回城没有其余打断操作的或,那么就会回城成功 -
如果当你移动打断了回城,再进行回城时,就需要重新
读条
,一般这种功能就称之为是防抖
-
-
或者我们都遇到过这样的场景,在某个搜索框中输入自己想要搜索的内容:
-
比如想要搜索 nekoaimer
- 当我输入n时,为了更好的用户体验,通常会出现对应的联想内容,这些联想内容通常是保存在服务器的,所以需要一次网络请求;
- 当继续输入ne时,再次发送网络请求;
- 那么nekoaimer一共需要发送9次网络请求;
- 这大大损耗我们整个系统的性能,无论是前端的事件处理,还是对于服务器的压力;
-
但是我们需要这么多次的网络请求吗?
- 不需要,正确的做法应该是在合适的情况下再发送网络请求;
- 比如如果用户快速的输入一个nekoaimer,那么只是发送一次网络请求;
- 比如如果用户是输入一个n想了一会儿,这个时候n确实应该发送一次网络请求;
- 也就是我们应该监听用户在某个时间,比如500ms内,没有再次触发时间时,再发送网络请求;
-
这就是防抖的操作:只有在某个时间内,没有再次触发某个函数时,才真正的调用这个函数;
Underscore 库的介绍
- 事实上我们可以通过一些第三方库来实现防抖操作:
- lodash
- underscore
- 这里使用underscore
- 我们可以理解成lodash是underscore的升级版,它更重量级,功能也更多;
- 但是目前我看到underscore还在维护,lodash已经很久没有更新了;
- Underscore的官网: underscorejs.org/
- Underscore的安装有很多种方式:
- 下载Underscore,本地引入;
- 通过CDN直接引入;
- 通过包管理工具(npm)管理安装;
- 这里我们直接通过CDN引入:
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.1/underscore-umd-min.js"></script>
HTML
<input type="text">
500ms
内有输入就不会触发
const inputEl = document.querySelector("input")
let counter = 0
inputEl.oninput = _.debounce((event) => console.log(`发送了${++counter}网络请求`, this, event), 500)
debounce v1 基本实现
HTML
结构
<input type="text">
const inputEl = document.querySelector("input")
let counter = 0
inputEl.oninput = debounce((event) => console.log(`发送了${++counter}网络请求`, this, event), 500)
Javascript
代码
function debounce(fn, delay) {
// 1.定义一个定时器, 保存上一次的定时器
let timer = null
// 2.真正执行的函数
const _debounce = function() {
// 3.取消上一次的定时器
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
// 4.外部传入要执行的函数
fn()
}, delay)
}
return _debounce
}
- 这样也能实现, 但是
this
都是指向window
,而且如果传了参数也没有处理
debounce v2 this参数
- 实现
this
指向与参数传递
function debounce(fn, delay) {
// 1.定义一个定时器, 保存上一次的定时器
let timer = null
// 2.真正执行的函数 并用args接收event对象或参数
const _debounce = function(...args) {
// 3.取消上一次的定时器
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
// 4.外部传入要执行的函数 传入this并将event对象或参数传入
fn.apply(this, args)
}, delay)
}
return _debounce
}
debounce v3 立即执行
- 可以控制是否立即执行,默认不是立即执行
- 这里的立即执行指的是:
当第一次输入后,会立即执行一次,后续在不超过delay时不会执行,而在超过了delay时才会执行。然后过了一会儿如果你再接着输入就会又立即执行一次,而在超过了delay时才会执行。依次类推!
function debounce(fn, delay, immediate = false) {
// 1.定义一个定时器, 保存上一次的定时器
let timer = null
// 2.判断是否立即执行过
let isInvoke = false
// 3.执行的函数
const _debounce = function (...args) {
// 4.取消上一次的定时器
if (timer) clearTimeout(timer)
// 5.每次第一次执行就会立即调用
if (immediate && !isInvoke) {
fn.apply(this, args)
return isInvoke = true
}
// 6.如果不是第一次执行就会延时调用
timer = setTimeout(() => {
fn.apply(this, args)
isInvoke = false
}, delay)
}
return _debounce
}
debounce v4 取消功能
取消功能
一般用于停止发送网络请求,比如:- 当用户输入信息还没到
delay
时,进行了关闭页面操作,或者推出登录操作 - 此时就没有必要再向后端数据库发送请求增加服务器压力了
- 当用户输入信息还没到
function debounce(fn, delay, immediate = false) {
// 1.定义一个定时器, 保存上一次的定时器
let timer = null
// 2.判断是否立即执行过
let isInvoke = false
// 3.执行的函数
const _debounce = function (...args) {
// 4.取消上一次的定时器
if (timer) clearTimeout(timer)
// 5.每次第一次执行就会立即调用
if (immediate && !isInvoke) {
fn.apply(this, args)
return isInvoke = true
}
// 6.如果不是第一次执行就会延时调用
timer = setTimeout(() => {
fn.apply(this, args)
isInvoke = false
}, delay)
}
// 封装取消功能
_debounce.cancel = function () {
if (timer) clearTimeout(timer)
// 初始化变量
timer = null
isInvoke = false
}
return _debounce
}
- 这里可使用代码进行简单测试:
HTML测试代码
<input type="text">
<button id="cancel">取消</button>
js测试代码
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
// 计数(网络请求次数)
let counter = 0
// 下面的oninput事件
const inputChange = (event) => console.log(`发送了${++counter}网络请求`, this, event)
// 拿到debounce返回值 _debounce函数
const debounceChange = debounce(inputChange, 500)
// oninput事件
inputEl.oninput = debounceChange
// 点击按钮取消事件
cancelBtn.onclick = function () {
debounceChange.cancel()
}
debounce v5 函数返回值
思路一:回调函数
function debounce(fn, delay, immediate = false, resultCallback) {
// 1.定义一个定时器, 保存上一次的定时器
let timer = null
// 2.判断是否立即执行过
let isInvoke = false
// 3.执行的函数
const _debounce = function (...args) {
// 4.取消上一次的定时器
if (timer) clearTimeout(timer)
// 5.每次第一次执行就会立即调用
if (immediate && !isInvoke) {
const result = fn.apply(this, args)
// 6.如果传入了回调函数 则将返回值传入给回调函数
if(resultCallback) resultCallback(result)
return isInvoke = true
}
// 7.如果不是第一次执行就会延时调用
timer = setTimeout(() => {
const result = fn.apply(this, args)
// 8.如果传入了回调函数 则将返回值传入给回调函数
if(resultCallback) resultCallback(result)
isInvoke = false
}, delay)
}
// 封装取消功能
_debounce.cancel = function () {
if (timer) clearTimeout(timer)
// 初始化变量
timer = null
isInvoke = false
}
return _debounce
}
- 这里可使用代码对
resultCallback实现的返回值功能
进行简单测试: HTML测试代码
<input type="text">
<button id="cancel">取消</button>
js测试代码
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
// 计数(网络请求次数)
let counter = 0
// 下面的oninput事件
const inputChange = (event) => {
console.log(`发送了${++counter}网络请求`, this, event)
// 返回值:返回 0-99 随机一个属
return ~~(Math.random() * 100)
}
// 拿到debounce返回值 _debounce函数
const debounceChange = debounce(inputChange, 500, false, function (res) {
console.log("resultCallback的返回值结果:", res)
})
// oninput事件
inputEl.oninput = debounceChange
// 点击按钮取消事件
cancelBtn.onclick = function () {
debounceChange.cancel()
}
思路二:Promise
function debounce(fn, delay, immediate = false, resultCallback) {
// 1.定义一个定时器, 保存上一次的定时器
let timer = null
// 2.判断是否立即执行过
let isInvoke = false
// 3.执行的函数
const _debounce = function (...args) {
// 4.通过返回Promise 传入返回结果
return new Promise((resolve, reject) => {
// 5.取消上一次的定时器
if (timer) clearTimeout(timer)
// 6.每次第一次执行就会立即调用
if (immediate && !isInvoke) {
const result = fn.apply(this, args)
// 7.如果传入了回调函数 则将返回值传入resolve 抛出错误的话传给reject
try {
if(resultCallback && typeof resultCallback === 'function') resolve(result)
} catch (err) {
reject(err)
}
return isInvoke = true
}
// 8.如果不是第一次执行就会延时调用
timer = setTimeout(() => {
const result = fn.apply(this, args)
// 9.如果传入了回调函数 则将返回值传入resolve 抛出错误的话传给reject
try {
if(resultCallback && typeof resultCallback === 'function') resolve(result)
} catch (err) {
reject(err)
}
isInvoke = false
}, delay)
})
}
// 10.封装取消功能
_debounce.cancel = function () {
if (timer) clearTimeout(timer)
// 11.初始化变量
timer = null
isInvoke = false
}
return _debounce
}
- 这里可使用代码对
Promise实现的返回值功能
进行简单测试: HTML测试代码
<input type="text">
<button id="cancel">取消</button>
js测试代码
const inputEl = document.querySelector("input")
const cancelBtn = document.querySelector("#cancel")
// 计数(网络请求次数)
let counter = 0
// 下面的oninput事件
const inputChange = (event) => {
console.log(`发送了${++counter}网络请求`, this, event)
// 返回值:返回 0-99 随机一个属
return ~~(Math.random() * 100)
}
// 拿到debounce返回值 _debounce函数
const debounceChange = debounce(inputChange, 500, true, function () { })
// 通过零食函数从内部拿到debounce函数返回的Promise
const tempCallback = (...args) => {
debounceChange.apply(inputEl, args).then(res => {
console.log("Promise的返回值结果:", res)
})
}
// oninput事件
inputEl.oninput = tempCallback
// 点击按钮取消事件
cancelBtn.onclick = function () {
debounceChange.cancel()
}