函数防抖
在指定的时间间隔范围内,不会重复触发回调函数,只有大于该时间间隔时才会触发,从而减少频繁触发次数
原理
- 通过定时器,让事件在触发后,经过指定长度的时间才去执行回调函数
- 而在这个等待的时间内,如果再次触发了事件,则清理当前定时器,重开一个新的定时器
- 这样,永远只有最后一次触发的事件会执行回调函数
- 防抖的意义是防止多次无意义地执行回调函数(是否无意义,根据具体情况而定)
应用场景
- 手风琴展示项的切换
- 轮播图切换
- 搜索框输入时的备选菜单
- 鼠标移动、页面滚动等触发频繁的事件
实现代码
function debounce(delay, cb) {
let timer
// 此返回值函数就是正常绑定事件时的回调函数,所以不能使用箭头函数
// 否则其this将始终指向window对象
return function () {
clearTimeout(timer)
/*
给window.event重新赋值,因为当前返回值函数就是事件回调函数,
所以默认会接收到事件对象,arguments[0]就是当前的事件对象
*/
window.event = arguments[0]
// setTimeout的回调函数必须使用箭头函数,
// 这样其this就指向了外部的回调函数的this,即绑定事件的元素对象
// 箭头函数不绑定arguments对象,所以此处的arguments对象就是事件回调函数的arguments对象
timer = setTimeout(() => {
cb.apply(this, arguments)
}, delay)
}
}
document.getElementById('input').addEventListener(
'keyup', debounce(300, function (e) {
console.log(event, e, this)
})
)
函数节流
在规定时间内多次触发时间,只执行一次回调函数,
原理
定时器方式
- 设置一个标识
- 如果标识为true,执行事件回调,并将标识改为false
- 然后使用定时器,在指定时间后将标识改为true
- 这样在指定的时间之内,该回调函数只会触发一次
时间戳方式
- 第一次执行回调函数时,记录其时间戳
- 当再次执行时,先判断当前时间戳距离上次执行时的时间戳是否大于了规定时间
- 如果大于,才能再次执行回调函数
实现代码
// 定时器方式实现
function throttle(delay, cb){
// 标识量,用来判断是否执行回调函数
let flag = true
return function (){
if(flag){
// 执行回调函数,并将标识量改为false,这样执行一次之后就不会再执行
cb.apply(this,arguments)
flag = false
}
// 定时器,在指定时间后,将标识量改为true,这样就可以再次执行回调函数
setTimeout(() => {
flag = true
}, delay)
}
}
// 时间戳方式实现
function throttle(delay, cb){
// 先获取时间戳作为判断依据
const firstTime = Date.now()
return function(){
// 只有本次执行时间距离上次执行时间的间隔大于等于规定时间,才能再次执行回调函数
if(Date.now() - firstTime >= delay){
cb.apply(this,arguments)
// 每次执行成功,更新时间戳
firstTime = Date.now()
}
}
}
document.querySelector('.key').addEventListener(
'keyup', throttle(500, function (e) {
console.log(event, e, this.value)
})
)
总结
- 防抖和节流,其实从本质上来讲,目的是一样的,都是减少回调函数的执行次数
- 只是实现方式和落脚点不同,一个是频繁触发只执行最后一次,一个是规定时间内只执行一次
- 所以如何选择,还是要看具体的业务场景
- 如果是一顿操作猛如虎,最后只要执行一次,那就用防抖,最典型的就是搜索框,一连串输入结束后,再发送Ajax请求去服务器查询相应的备选列表
- 如果是周期性的执行一次回调,那肯定选节流了