防抖和节流
共同点:都是为了限制函数的执行次数,避免短时间内不必要的多次函数执行,导致响应速度跟不上触发频率,引起页面卡顿
防抖(debounce)
基本思想:高频率触发的事件,在触发事件停下的一段时间后执行一次函数,若在这段时间内事件再次触发,则重新开始计算周期,其特点就是事件连续触发时只执行一次函数,执行完函数后再次触发事件,开启新的周期
延迟debounce:周期结束时执行动作
前缘debounce:周期开始时执行动作
延迟debounce使用场景:输入框输入时进行某些操作,比如调接口、处理数据格式、自动补全关键字等,都可以在暂停连续输入的时候才操作
下面是debounce代码,immediate控制是前缘还是延迟debounce
function debounce(fn, delay, immediate) {
let timer, timestamp = 0, args, context
let run = function() {
timer = setTimeout(function() {
// 改变this指向调用debounce的上下文环境
if(!immediate) {
fn.apply(context, args)
}
}, delay)
}
return function() {
args = arguments
context = this
// 获取当前时间戳
let now = new Date().getTime()
if(now - timestamp < delay) {
// 周期内触发事件,重置定时器,重新设置时间周期
clearTimeout(timer)
run()
} else {
if(immediate) {
fn.apply(context, args)
}
// 两次触发事件的时间间隔大于时间周期,则开启新的时间周期
run()
}
timestamp = now
}
}
function handle() {
console.log('执行事件')
}
window.addEventListener('scroll', debounce(handle, 1000))
简化代码
function debounce(fn, delay) {
let timer = null
return function() {
if(timer) {
// 触发事件,清除定时器,重新计时
clearTimeout(timer)
timer = null
}
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
优化代码,减少设定定时器的次数,在执行定时器的代码过程中判断起始时间的变化,若变化了则需要设置延时定时器,延时定时器的时间长度等于delay - now - startTimestamp, 也就是整个过程中必须保证事件触发停止一段时间(delay)后才执行函数
function debounce(fn, delay, immediate) {
let timer, startTimestamp, args, context
let run = function(wait) {
timer = setTimeout(() => {
const now = new Date().getTime()
const interval = now - startTimestamp
if(interval < wait) {
// 如果周期内事件触发,开始时间戳会增加,则差值必然小于wait
startTimestamp = now
run(delay - interval)
} else {
if(!immediate) {
fn.apply(context, args)
}
clearTimeout(timer)
timer = null
}
}, wait)
}
return function() {
args = arguments
context = this
// 事件触发,则更新起始时间
startTimestamp = new Date().getTime()
if(!timer) {
if(immediate) {
fn.apply(context, args)
}
run(delay)
}
}
}
节流
基本思想:高频率触发的事件,周期时间内只执行一次动作,稀释了执行动作的频率
特点:事件连续触发的时候,只在到达时间周期才执行动作,响应平滑,周期结束后,事件触发再次开始新的时间周期
使用场景:监听滚动条滚动事件,滚动时根据滚动的高度给元素增加某些样式或动画效果,页面滚动到底部加载更多
// immediate开启前缘
function throttle(fn, delay, immediate) {
let timer = null
return function() {
if(timer) return
if(immediate) fn.apply(this, arguments)
timer = setTimeout(() => {
if(!immediate) {
fn.apply(this, arguments)
}
clearTimeout(timer)
timer = null
}, delay)
}
}
function handle() {
console.log('执行事件')
}
window.addEventListener('scroll', throttle(handle, 1000))