# 认识防抖 debounce 函数
- 我们来理解一下它的过程:
- 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间
- 当事件密集触发时,函数的触发会被频繁的推迟
- 只有等待了一段时间也没有事件触发,才会真正的执行响应函数
- 防抖的应用场景很多:
- 输入框中频繁的输入内容,搜索或者提交信息;
- 频繁的点击按钮,触发某个事件
- 监听浏览器滚动事件,完成某些特定操作
- 用户缩放浏览器的 resize 事件
# 防抖函数的案例
# Underscore 库的介绍
- 事实上我们可以通过一些第三方库来实现防抖操作:
- 这里使用 underscore
- 我们可以理解成 lodash 是 underscore 的升级版,它更重量级,功能也更多;
- 但是目前我看到 underscore 还在维护,lodash 已经很久没有更新了;
- Underscore 的官网: https://underscorejs.org/
- Underscore 的安装有很多种方式:
- 下载 Underscore,本地引入;
- 通过 CDN 直接引入;
- 通过包管理工具(npm)管理安装;
- 这里我们直接通过 CDN 引入:
| <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.1/underscore-umd-min.js"></script> |
| const inputEl = document.querySelector("input") |
| let counter = 0 |
| |
| inputEl.oninput = _.debounce((event) => console.log(`发送了${++counter}网络请求`, this, event), 500) |
# debounce v1 基本实现
| const inputEl = document.querySelector("input") |
| let counter = 0 |
| inputEl.oninput = debounce((event) => console.log(`发送了${++counter}网络请求`, this, event), 500) |
| function debounce(fn, delay) { |
| |
| let timer = null |
| |
| |
| const _debounce = function() { |
| |
| if (timer) clearTimeout(timer) |
| |
| timer = setTimeout(() => { |
| |
| fn() |
| }, delay) |
| } |
| |
| return _debounce |
| } |
- 这样也能实现, 但是
this
都是指向 window
,而且如果传了参数也没有处理
# debounce v2 this 参数
| function debounce(fn, delay) { |
| |
| let timer = null |
| |
| |
| const _debounce = function(...args) { |
| |
| if (timer) clearTimeout(timer) |
| |
| timer = setTimeout(() => { |
| |
| fn.apply(this, args) |
| }, delay) |
| } |
| |
| return _debounce |
| } |
# debounce v3 立即执行
- 可以控制是否立即执行,默认不是立即执行
- 这里的立即执行指的是:
当第一次输入后,会立即执行一次,后续在不超过delay时不会执行,而在超过了delay时才会执行。然后过了一会儿如果你再接着输入就会又立即执行一次,而在超过了delay时才会执行。依次类推!
| function debounce(fn, delay, immediate = false) { |
| |
| let timer = null |
| |
| |
| let isInvoke = false |
| |
| |
| const _debounce = function (...args) { |
| |
| |
| if (timer) clearTimeout(timer) |
| |
| |
| if (immediate && !isInvoke) { |
| fn.apply(this, args) |
| return isInvoke = true |
| } |
| |
| |
| timer = setTimeout(() => { |
| fn.apply(this, args) |
| isInvoke = false |
| }, delay) |
| } |
| |
| return _debounce |
| } |
# debounce v4 取消功能
取消功能
一般用于停止发送网络请求,比如:
- 当用户输入信息还没到
delay
时,进行了关闭页面操作,或者推出登录操作
- 此时就没有必要再向后端数据库发送请求增加服务器压力了
| function debounce(fn, delay, immediate = false) { |
| |
| let timer = null |
| |
| |
| let isInvoke = false |
| |
| |
| const _debounce = function (...args) { |
| |
| |
| if (timer) clearTimeout(timer) |
| |
| |
| if (immediate && !isInvoke) { |
| fn.apply(this, args) |
| return isInvoke = true |
| } |
| |
| |
| timer = setTimeout(() => { |
| fn.apply(this, args) |
| isInvoke = false |
| }, delay) |
| } |
| |
| |
| _debounce.cancel = function () { |
| if (timer) clearTimeout(timer) |
| |
| |
| timer = null |
| isInvoke = false |
| } |
| |
| return _debounce |
| } |
| <input type="text"> |
| <button id="cancel">取消</button> |
| const inputEl = document.querySelector("input") |
| const cancelBtn = document.querySelector("#cancel") |
| |
| |
| let counter = 0 |
| |
| |
| const inputChange = (event) => console.log(`发送了${++counter}网络请求`, this, event) |
| |
| |
| const debounceChange = debounce(inputChange, 500) |
| |
| |
| inputEl.oninput = debounceChange |
| |
| |
| cancelBtn.onclick = function () { |
| debounceChange.cancel() |
| } |
# debounce v5 函数返回值
# 思路一:回调函数
| function debounce(fn, delay, immediate = false, resultCallback) { |
| |
| let timer = null |
| |
| |
| let isInvoke = false |
| |
| |
| const _debounce = function (...args) { |
| |
| if (timer) clearTimeout(timer) |
| |
| |
| if (immediate && !isInvoke) { |
| const result = fn.apply(this, args) |
| |
| |
| if(resultCallback) resultCallback(result) |
| return isInvoke = true |
| } |
| |
| |
| timer = setTimeout(() => { |
| const result = fn.apply(this, args) |
| |
| |
| 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> |
| const inputEl = document.querySelector("input") |
| const cancelBtn = document.querySelector("#cancel") |
| |
| let counter = 0 |
| |
| const inputChange = (event) => { |
| console.log(`发送了${++counter}网络请求`, this, event) |
| |
| return ~~(Math.random() * 100) |
| } |
| |
| const debounceChange = debounce(inputChange, 500, false, function (res) { |
| console.log("resultCallback的返回值结果:", res) |
| }) |
| |
| inputEl.oninput = debounceChange |
| |
| cancelBtn.onclick = function () { |
| debounceChange.cancel() |
| } |
# 思路二:Promise
| function debounce(fn, delay, immediate = false, resultCallback) { |
| |
| let timer = null |
| |
| |
| let isInvoke = false |
| |
| |
| const _debounce = function (...args) { |
| |
| return new Promise((resolve, reject) => { |
| |
| if (timer) clearTimeout(timer) |
| |
| |
| if (immediate && !isInvoke) { |
| const result = fn.apply(this, args) |
| |
| |
| try { |
| if(resultCallback && typeof resultCallback === 'function') resolve(result) |
| } catch (err) { |
| reject(err) |
| } |
| |
| return isInvoke = true |
| } |
| |
| |
| timer = setTimeout(() => { |
| const result = fn.apply(this, args) |
| |
| |
| try { |
| if(resultCallback && typeof resultCallback === 'function') resolve(result) |
| } catch (err) { |
| reject(err) |
| } |
| |
| isInvoke = false |
| }, delay) |
| }) |
| } |
| |
| |
| _debounce.cancel = function () { |
| if (timer) clearTimeout(timer) |
| |
| |
| timer = null |
| isInvoke = false |
| } |
| |
| return _debounce |
| } |
- 这里可使用代码对
Promise实现的返回值功能
进行简单测试:
HTML测试代码
| <input type="text"> |
| <button id="cancel">取消</button> |
| const inputEl = document.querySelector("input") |
| const cancelBtn = document.querySelector("#cancel") |
| |
| |
| let counter = 0 |
| |
| |
| const inputChange = (event) => { |
| console.log(`发送了${++counter}网络请求`, this, event) |
| |
| return ~~(Math.random() * 100) |
| } |
| |
| |
| const debounceChange = debounce(inputChange, 500, true, function () { }) |
| |
| |
| const tempCallback = (...args) => { |
| debounceChange.apply(inputEl, args).then(res => { |
| console.log("Promise的返回值结果:", res) |
| }) |
| } |
| |
| |
| inputEl.oninput = tempCallback |
| |
| |
| cancelBtn.onclick = function () { |
| debounceChange.cancel() |
| } |