防抖节流前端很实用的工具方法,真是写了一遍又一遍,直接上代码
防抖走起:
function debounce(cb, t, ctx) {
let timer;
return function (...args) {
clearTimeout(timer)
timer = setTimeout(function () {
cb.apply(ctx || null, args)
}, t)
}
}
节流也少不了
function throttle(cb, t, ctx) {
let now = 0
return function (...args) {
let curNow = Date.now()
if ((curNow - now) > t) {
cb.apply(ctx, args)
now = curNow;
}
}
}
上面防抖和节流都优化了传递this(ctx参数),当然你用的是箭头函数就没有效果了。
由于防抖机制是延迟回调,在实际项目中一般都用于按钮,在项目中并不想要按下按钮还需要等待一段时间(这个时间机制比较尴尬,太短了还是有重复触发的机会,太长了你懂得)
所以项目里写了一个立即执行的防抖函数,即下面的代码(有一个不算bug的bug)
//执行顺序第一次是秒触发,第二次可触发的情况下也是秒触发,第三次可执行的情况下需要等待延迟回调才行。
function imeDebounce(cb, t, ctx) {
let timer,
count = 0,
now = Date.now();
return function (...args) {
//第一次执行
if (count === 0) {
cb.apply(ctx, args)
//第一次执行的时间戳
now = Date.now();
count++
} else if (count === 1) {
//第二次时间戳和第一次时间戳比较
let curNow = Date.now();
if (curNow - now > t) {
//当前执行通过,更新闭包时间戳,
cb.apply(ctx, args)
count++
now = Date.now();
} else {
//这里是不在可执行的时间,要重新赋值闭包的时间戳
now = curNow
}
} else {
//先对比时间戳,如果时间不允许则更新闭包时间戳
let curNow = Date.now();
if (curNow - now < t) {
now = curNow
return
}
//这里才是真正的延迟
timer && clearTimeout(timer)
timer = setTimeout(function () {
cb.apply(ctx || null, args)
}, t)
}
}
}
上面立即执行代码实际就是加了几个判断和定时器的组合,代码块开头注释已经说明了一个bug问题,这是不想要的。所以在上测试放空脑子的时候想到了第二种方法
function mutateImeDebounce(cb, t, ctx) {
let count = 0,
now = 0;
return function (...args) {
if (count === 0) {
//count为0当前时间戳也为0,则是第一次执行(立即执行)
if (now === 0) {
cb.apply(ctx, args)
//更新时间戳和count值
now = Date.now();
count++
} else {
//如果当前时间戳不为0,则是通过改变count为0,这里再次判断时间戳是否过期。
let curNow = Date.now();
//未过期则重新赋值时间戳
if (curNow - now < t) {
now = curNow
} else {
//过期了则执行。
cb.apply(ctx, args)
//更新时间戳
now = Date.now();
count++
}
}
} else if (count === 1) {
//第二次时间戳和第一次时间戳比较
let curNow = Date.now();
if (curNow - now < t) {
//未过期则重新赋值时间戳
now = curNow
} else {
//当前执行通过,更新闭包时间戳,
cb.apply(ctx, args)
count = 0
now = Date.now();
}
}
}
}
这种方式不再有延迟回调的形式,每次可执行都是立即执行。整体代码逻辑就是每次执行都是更新闭包保存的时间戳,然后每次执行对比时间戳不过期就不执行,再判断一下边界情况就是第一次执行。