# 防抖与截流
- 可参考underscore,lodash通用工具库
# 函数防抖-限制操作频率的函数
- 防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
- 适用场景:
- 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
- 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
- 生产环境请用lodash.debounce
# 函数截流
- 防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效
- 适用场景:
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器resize
- 动画场景:避免短时间内多次触发动画引起性能问题
# 异同点
- 相同:在不影响客户体验的前提下,将频繁的回调函数,进行次数缩减.避免大量计算导致的页面卡顿.
- 不同:防抖是将多次执行合并为最后一次执行,节流是将多次执行变为在规定时间内只执行一次.
# 防抖代码实现
- 参考链接:
- https://juejin.im/post/5c6bab91f265da2dd94c9f9e
- https://github.com/mqyqingfeng/Blog/issues/22
- https://www.codercto.com/a/36013.html
- 防抖分为两种:
- 1)非立即执行版:事件触发->延时->执行回调函数;如果在延时中,继续触发事件,则会重新进行延时.在延时结束后执行回调函数.常见例子:就是input搜索框,客户输完过一会就会自动搜索
- 2)立即执行版:事件触发->执行回调函数->延时;如果在延时中,继续触发事件,则会重新进行延时.在延时结束后,并不会执行回调函数.常见例子:就是对于按钮防点击.例如点赞,心标,收藏等有立即反馈的按钮,还有下拉加载.
$('textarea').on('keydown', debounce(ajaxAction, 2500));
// 保证了回调函数之间的调用间隔,至少是delay
//简版
function debounce(func, delay){
let timer = null // 声明计时器
return function(...args){
clearTimeout(timer)
timer = setTimeout(()=>{
func.apply(this, args)
}, delay)
}
}
function debounce(fn, delay, immediate){
let timer = null
return function(...args){
clearTimeout(timer)
if(immediate){
fn.apply(this,args)
timer = setTimeout(()=>{
timer = null
}, delay)
}else{
timer = setTimeout(()=>{
fn.apply(this, args)
}, delay)
}
}
}
// 带有立即执行选项的防抖函数
const debounce = (func, delay, immediate) => {
let timer // 保存定时器
// 返回的闭包不要使用箭头函数,这样可以根据实际情况更加灵活的使用this
return function(...args){
if(timer){
clearTimeout(timer) //不管是否立即执行,都要先清空定时器
}
if(immediate){
// 如果是立即执行,则定时器中不再包含回调函数,
// 而是在回调函数执行后,仅起到延时和销毁定时器的作用
if(!timer) func.apply(this, args)
timer = setTimeout(() => {
timer = null
}, delay)
}else{
// 如果是非立即执行,则重新设定定时器,并将回调函数放入其中
// setTimeout的回调函数用箭头函数来写,可以直接绑定外层的this
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
}
# 截流代码实现
// 时间戳版
function throttle(fn, delay){
let previous = 0
return function(...args){
let now = Date.now() // 记录此刻触发时的时间戳
if(now - previous > delay){// 时间差大于规定时间,则触发
fn.apply(this, args)
previous = now
}
}
}
// 定时器版
function throttle(fn, delay){
let timer
return function(...args){
if(!timer){
timer = setTimeout(() => {
timer = null
fn.apply(this, args)
}, delay)
}
}
}
// 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
// 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件
// 时间戳加定时器版,这版有问题
function throttle(fn, delay){
let timer
let previous = 0
return function(...args){
let now = Date.now()
if(now - previous >= delay){
fn.apply(this, args)
previous = now
}else{
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}
}