防抖与节流
简介
防抖与节流是前端开发中一个重要的概念,有的开发人员,觉得写不写都无所谓,那是因为现在的计算机硬件比以前强太多了,代码是在后台运行的,对于用户来说电脑的cpu只要没干冒烟,用户是无感知的,当发起大量无用请求的时候,服务器会累的够呛,防抖与节流就是用来过滤高频无用操作的。
防抖
在讲节流之前,我想先讲讲防抖,因为节流在实现上有自身的缺陷需要防抖来弥补。何为防抖,是防止抖动吗?遇事不决先查词典,我们发现bounce这个单词的意思是抖动,也有上下跳动的意思,我们类比到用户的操作上,用户的操作无非就是点击,滑动,当用户高频触发这类事件时,触发时机是无规律的,就好比上下抖动,所以我们用deBounce来表示前端的防抖,所以在有限的时间内我们只要执行最后一次事件的操作就行,因为之前的操作对我根本就没意义。
思路:
利用定时器设置要延迟多久执行操作,如果在这期间内又触发了操作,那么我们要注销掉之前的定时器,并重新设置一个定时器,以此类推,直到用户在延迟的时间内没有操作,定时器运行后,那么我们至此就完成了一次防抖,直接上代码!
/**
* @description 一个防抖函数
* @param { Function } - 需要添加防抖功能的函数
* @delay { number } - 防抖延迟的时间
*/
export default function deBounce(fun, delay) {
var timer = 0 // 初始化定时器的id,定时器在创建的时候会返回一个大于0的id
return function() {
var args = arguments // 获取参数
var context = this // 保存上下文,防止this指向发生问题
clearTimeout(timer) // 清除定时任务,之后设置一个新的定时任务,因为防抖只需指向最后一次
timer = setTimeout(function(){
fun.apply(context, args) // 绑定上下文再指向,防止this丢失
})
}
}
防抖补充: 这个防抖如果仔细看的话有那么一点小问题,就是如果delay设置的时间很长的话,可能会导致首次点击看上去好像没有反应,如果对首次点击有要求的话这个防抖函数就不太适用了;我们可以引入一个flag,默认为true,如果这个flag为true的话就执行,执行完成之后就把falg设置为false,直接看代码。
/**
* @description 首次执行版本
* @param { Function } - 需要添加防抖功能的函数
* @delay { number } - 防抖延迟的时间
*/
export default function deBounce(fun, delay) {
var timer = 0
var flag = true // 是否允许执行
return function(){
var args = arguments
var context = this
if(flag) {
fun.apply(context, args)
flag = false
return
}
clearTimeout(timer)
timer = setTimeout(function(){
fun.apply(context, args)
flag = true
},delay)
}
}
节流
节流比防抖麻烦很多,节流是在指定的时间内如论如何都会执行一次,打个比方,如果我设置节流的延时是1秒,那么我每250毫米就触发一次,我连续触发了8次也就是2s那么只会执行2次,而且这两次的间隔是1s,节流有多种实现方式,并且节流分首节流和尾节流
首节流:
既然要在指定的时间内必须执行一次,那么我们可以设置一个标记变量flag用来表示是否可以执行,默认为可以执行,执行完成之后我们立马把标记变量设置为不能执行状态,之后设置定时器,当经过指定时间之后再把标记变量设置为可执行状态,那么用户点击的时候又能执行了。
/**
* @description 节流函数(首节流,第一次触发会立即执行)
* @param { Function } - 要添加节流功能的函数
* @param { number } - 要延迟的毫秒数
*/
export function firstThrottle(fun, delay) {
var flag = true // 是否可以执行
return function(){
if(!flag) return // 不能执行就直接返回
// 获取参数和上下文
var args = arguments
var context = this
fun.apply(context, args)
// 执行完成之后,把flag设置成false表示不能执行,并设置定时器,delay毫秒之后设置true
flag = false
setTimeout(function(){
flag = true
}, delay);
}
}
尾节流:
尾节流和首节流实现思路类似,只是尾节流,会先设置定时器,当定时器执行完成之后才设置状态。
/**
* @description 节流函数(尾节流,无论延迟设置多久,总会执行最后一次)
* @param { Function } - 要添加节流功能的函数
* @param { number } - 要延迟的毫秒数
*/
export function lastThrottle(fun, delay) {
var timer = 0
return function () {
if(timer) return
var args = arguments
var context = this
timer = setTimeout(function () {
fun.apply(context, args)
timer = 0
},delay)
}
}
时间戳实现节流:
首先保存之前执行的时间戳per,默认值设为0,当函数被调用时设置变量cur为Date.now(),把cur-per与delay比较,如果大于的话就执行,否则就不执行,执行之后把cur的时间戳给per,用于下次触发做差。
/**
* @description 节流函数(首节流,通过时间戳实现)
* @param { Function } - 要添加节流功能的函数
* @param { number } - 要延迟的毫秒数
*/
export function lastThrottleByTime(fun, delay) {
var pre = 0
return function () {
var cur = Date.now() // 获取当前的时间戳
if(cur - pre > delay) {
var args = arguments
var context = this
fun.apply(context, args)
pre = cur
}
}
}
思考:
首节流适用delay大且对最终状态无要求的功能,缺点是不会执行最终状态。
尾节流适用delay不大且对最终状态有要求的功能,缺点是,当delay大时用户会认为自己的第一次点击好像无效。
有没有什么办法可以是首节流,又保留最终状态?之前我们讲过防抖,防抖在高频率点击下,只会触发最后一次,这不就是我们要的尾状态吗!
/**
* @description 节流函数(首尾节流,通过时间戳实现)
* @param { Function } - 要添加节流功L的函数
* @param { number } - 要延迟的毫秒数
*/
export function firstAndLastThrottle(fun, delay) {
var pre = 0
var timer = 0
var _delay = delay + 100 // 防抖用的时间间隔,比节流大0.1秒,因为人对0.1秒的延迟无感知
return function () {
var args = arguments
var context = this
var cur = Date.now() // 获取当前的时间戳
if(cur - pre > delay) {
fun.apply(context, args)
pre = cur
}
clearTimeout(timer)
timer = setTimeout(() => {
fun.apply(context, args)
}, _delay);
}
}
总结:
节流和防抖是两种处理高频操作减频的方式,在实际开发中我们要根据不同的情况合理使用防抖和节流,像输入搜索提示我们可以使用防抖来避免内容还没输入完整就发送了所有请求,对于监听屏幕大小变化做响应式处理我们可以使用节流。
评论区