防抖
防抖策略:置事件被触发后,延迟n秒后在执行回调,如果在和 n 秒内又被触发,则重新记时
防抖应用场景
- 用户在输入框中连续输入一串字符时,可以通过防抖策略,只在输入完成后,才执行查询请求,这样可以有效减少请求次数,节约请求资源,经常用于输入框中。
- 频繁的点击按钮,触发某个事件
- 监听浏览器滚动事件
- 用户缩放浏览器的
resize事件
手写防抖函数
最基本的防抖
function debounce(fn, delay) {
let timer = null
const _debounce = function () {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn()
}, delay)
}
return _debounce
}
绑定this与event
function debounce(fn, delay) {
let timer = null
const _debounce = function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
return _debounce
}
使用
const inputEl = document.querySelector('input')
let counter = 0
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event);
}
// 防抖
inputEl.oninput = debounce(inputChange, 1000)
防抖立即执行
如果我们想让防抖函数立即执行一次,我们该怎么办?
只有第一次立即执行,当时第三个参数为true时,立即执行
function debounce(fn, delay, immediate = false) {
let timer = null
const _debounce = function (...args) {
if (timer) clearTimeout(timer)
// 是否需要立即执行
if (immediate) {
fn.apply(this, args)
immediate = false
} else {
// 延迟执行
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
return _debounce
}
延时器输出后,再次输入还会立即执行一次
function debounce(fn, delay, immediate = false) {
let timer = null
let isInvoke = false
const _debounce = function (...args) {
if (timer) clearTimeout(timer)
// 是否需要立即执行
if (immediate) {
fn.apply(this, args)
immediate = false
} else {
// 延迟执行
timer = setTimeout(() => {
fn.apply(this, args)
isInvoke = true
}, delay)
}
}
return _debounce
}
封装取消功能
function debounce(fn, delay, immediate = false) {
let timer = null
let isInvoke = false
const _debounce = function (...args) {
if (timer) clearTimeout(timer)
// 是否需要立即执行
if (immediate) {
fn.apply(this, args)
immediate = false
} else {
// 延迟执行
timer = setTimeout(() => {
fn.apply(this, args)
isInvoke = true
}, delay)
}
}
// 封装取消功能
_debounce.cancel = function () {
if (timer) clearTimeout(timer)
timer = null
isInvoke = false
}
return _debounce
}
使用
<input type="text">
<button class="cancel">取消</button>
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector('input')
let counter = 0
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event);
}
const debounceChange = debounce(inputChange, 3000)
inputEl.oninput = debounceChange
// 取消
const cancelBtn = document.querySelector('.cancel')
cancelBtn.onclick = function () {
debounceChange.cancel()
}
</script>
函数含有返回值
如果需要防抖的函数有返回值(一般没有),我们该怎么办呢?
第一种实现:封装一个resultCallback函数
function debounce(fn, delay, immediate = false, resultCallback) {
let timer = null
let isInvoke = false
const _debounce = function (...args) {
if (timer) clearTimeout(timer)
// 是否需要立即执行
if (immediate) {
const result = fn.apply(this, args)
if (resultCallback && typeof resultCallback === 'function') {
resultCallback(result)
}
immediate = false
} else {
// 延迟执行
timer = setTimeout(() => {
const result = fn.apply(this, args)
if (resultCallback && typeof resultCallback === 'function') {
resultCallback(result)
}
isInvoke = true
}, delay)
}
}
return _debounce
}
使用
<input type="text">
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector('input')
let counter = 0
// 有返回值
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event);
return 'aaa'
}
const debounceChange = debounce(inputChange, 3000, false, res => {
console.log('返回值:', res);
})
inputEl.oninput = debounceChange
</script>
第二种实现:返回一个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) {
const result = fn.apply(this, args)
resolve(result)
immediate = false
} else {
// 延迟执行
timer = setTimeout(() => {
const result = fn.apply(this, args)
resolve(result)
isInvoke = true
}, delay)
}
})
}
return _debounce
}
使用
<input type="text">
<script src="./debounce.js"></script>
<script>
const inputEl = document.querySelector('input')
let counter = 0
// 有返回值
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event);
return 'aaa'
}
const debounceChange = debounce(inputChange, 3000, false)
const inputChangeReturn = () => {
debounceChange().then(res => {
console.log('返回值:', res);
})
}
inputEl.oninput = inputChangeReturn
</script>
节流
节流策略:可以减少一段时间内事件的触发频率
实现:闭包加上延时器
应用场景
- 鼠标连续不断地触发某事件(比如点击),只在单位时间内只触发一次
- 懒加载时要监听计算滚动条的位置,但不必么此滑动都要触发,可以降低计算的频率,不必去浪费CPU资源
- 监听页面的滚动事件
- 鼠标移动事件
- 游戏中的一些设计
节流阀
节流阀为空,表示可以执行下次操作,不为空,表示不执行下次操作,当前操作执行完,必须将节流阀重置为空,表示可以执行下次操作了,每次执行操作前,必须判断节流阀是否为空
通俗理解:高铁卫生间是否被占用,有红绿灯控制,红灯表示被占用,绿灯表示可使用,假设每个人上卫生间都需要花五分钟,则五分钟之内,被占用卫生间无法被其他人使用,上一个人使用完毕后,需要将红的重置为绿灯,表示下一个人可以使用卫生间,下一个人使用卫生间之前先判断控制灯是否为绿色,来判断能否可以上卫生间
手写节流函数
最基本的节流
function throttle(fn, interval) {
let lastTime = 0
const _throttle = function () {
const nowTime = new Date().getTime()
console.log(new Date(nowTime));
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
fn()
lastTime = nowTime
}
}
return _throttle
}
使用
<input type="text">
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector('input')
let counter = 0
// 有返回值
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event);
return 'aaa'
}
// 节流处理
inputEl.oninput = throttle(inputChange, 2000)
</script>
实现leading功能
leading为ture的时候,第一次回触发,为false不触发
function throttle(fn, interval, options = { leading: true, trailing: false }) {
const { leading, trailing } = options
let lastTime = 0
const _throttle = function () {
const nowTime = new Date().getTime()
if (lastTime === 0 && leading === false) lastTime = nowTime
// 与下面等价
// if (!lastTime && !leading) lastTime = nowTime
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
fn()
lastTime = nowTime
}
}
return _throttle
}
使用
inputEl.oninput = throttle(inputChange, 2000, { leading: false })
实现trailing
function throttle(fn, interval, options = { leading: true, trailing: false }) {
const { leading, trailing } = options
let lastTime = 0
let timer = null
const _throttle = function () {
const nowTime = new Date().getTime()
// leading为false remainTime为定义的时间,第一次输入就不会触发
if (lastTime === 0 && leading === false) lastTime = nowTime
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
fn()
lastTime = nowTime
return
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
// 如果leading为false lastTime=0,重新记时
// leading为true remainTime = interval - (nowTime - lastTime)>0 不会触发两次
lastTime = !leading ? 0 : new Date().getTime()
fn()
}, remainTime)
}
}
return _throttle
}
绑定this与event
function throttle(fn, interval, options = { leading: true, trailing: false }) {
const { leading, trailing } = options
let lastTime = 0
let timer = null
const _throttle = function (...args) {
const nowTime = new Date().getTime()
// leading为false remainTime为定义的时间,第一次输入就不会触发
if (lastTime === 0 && leading === false) lastTime = nowTime
// 与下面等价
// if (!lastTime && !leading) lastTime = nowTime
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
fn.apply(this, args)
lastTime = nowTime
return
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
// 如果leading为false lastTime=0,重新记时
// leading为true remainTime = interval - (nowTime - lastTime)>0 不会触发两次
lastTime = !leading ? 0 : new Date().getTime()
fn.apply(this, args)
}, remainTime)
}
}
return _throttle
}
实现取消功能
function throttle(fn, interval, options = { leading: true, trailing: false }) {
。。。与上面一样
_throttle.cancel = function () {
if (timer) clearTimeout(timer)
timer = null
lastTime = 0
}
return _throttle
}
使用
<input type="text">
<button class="cancel">取消</button>
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector('input')
let counter = 0
// 有返回值
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event);
return 'aaa'
}
// 节流处理
const _throttle = throttle(inputChange, 2000, { leading: true, trailing: false,res })
inputEl.oninput = _throttle
const cancelBtn = document.querySelector('.cancel')
cancelBtn.onclick = function () {
_throttle.cancel()
}
</script>
实现函数返回值
第一种实现方法:封装一个回调函数
function throttle(fn, interval, options = { leading: true, trailing: false }) {
const { leading, trailing, resultCallback } = options
let lastTime = 0
let timer = null
const _throttle = function (...args) {
const nowTime = new Date().getTime()
// leading为false remainTime为定义的时间,第一次输入就不会触发
if (lastTime === 0 && leading === false) lastTime = nowTime
// 与下面等价
// if (!lastTime && !leading) lastTime = nowTime
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
const result = fn.apply(this, args)
if (resultCallback) resultCallback(result)
lastTime = nowTime
return
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
// 如果leading为false lastTime=0,重新记时
// leading为true remainTime = interval - (nowTime - lastTime)>0 不会触发两次
lastTime = !leading ? 0 : new Date().getTime()
const result = fn.apply(this, args)
if (resultCallback) resultCallback(result)
}, remainTime)
}
}
_throttle.cancel = function () {
if (timer) clearTimeout(timer)
timer = null
lastTime = 0
}
return _throttle
}
使用
<input type="text">
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector('input')
let counter = 0
// 有返回值
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event);
return 'aaa'
}
// 节流处理
const _throttle = throttle(inputChange, 2000, {
leading: true,
trailing: false,
resultCallback: function (res) { console.log('返回值:', res); }
})
inputEl.oninput = _throttle
</script>
第二种实现:返回一个Promise
function throttle(fn, interval, options = { leading: true, trailing: false }) {
const { leading, trailing, resultCallback } = options
let lastTime = 0
let timer = null
const _throttle = function (...args) {
return new Promise((resolve, reject) => {
const nowTime = new Date().getTime()
// leading为false remainTime为定义的时间,第一次输入就不会触发
if (lastTime === 0 && leading === false) lastTime = nowTime
// 与下面等价
// if (!lastTime && !leading) lastTime = nowTime
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
const result = fn.apply(this, args)
resolve(result)
lastTime = nowTime
return
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
// 如果leading为false lastTime=0,重新记时
// leading为true remainTime = interval - (nowTime - lastTime)>0 不会触发两次
lastTime = !leading ? 0 : new Date().getTime()
const result = fn.apply(this, args)
resolve(result)
}, remainTime)
}
})
}
_throttle.cancel = function () {
if (timer) clearTimeout(timer)
timer = null
lastTime = 0
}
return _throttle
}
使用
<input type="text">
<script src="./throttle.js"></script>
<script>
const inputEl = document.querySelector('input')
let counter = 0
// 有返回值
const inputChange = function (event) {
console.log(`发送了第${++counter}次网络请求`, this, event);
return 'aaa'
}
// 节流处理
const _throttle = throttle(inputChange, 2000, { leading: true, trailing: false, })
// 因为函数调用的问题 需要我们绑定this _throttle().then为默认绑定
inputEl.oninput = (...args) => {
_throttle.apply(inputEl, args).then(res => {
console.log('返回值:', res);
})
}
</script>
区别
- 防抖:如果事件被频繁触发,防抖能保证只有最有一次触发生效,前面N多次的触发都会忽略
- 节流:如果事件被频繁触发,节流能够减少事件触发频率,因此,节流是有选择地执行一部分事件