一文读懂防抖和截流

本篇文章梗概:

什么是防抖和节流?
他们有什么区别?
分别如何实现?

 

什么是防抖和节流?
防抖和节流,都是开发过程中防止函数多次调用的方式。我现在写的主要是前端开发中的防抖和节流的介绍。

 

什么是防抖?
防抖,顾名思义,防止抖动,以免把一次事件误认为多次,敲键盘就是一个每天都会接触到的防抖操作。

想要了解一个概念,必先了解概念所应用的场景。在 JS 这个世界中,有哪些防抖的场景呢?

1. 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖。
2. 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖。
3. 文本编辑器实时保存,当无任何更改操作一秒后进行保存。

 

什么是节流?
节流,顾名思义,控制水的流量。控制事件发生的频率,如控制为1s发生一次,甚至1分钟发生一次。与服务端(server)及网关(gateway)控制的限流 (Rate Limit) 类似。

scroll 事件,每隔一秒计算一次位置信息等
浏览器播放事件,每个一秒计算一次进度信息等
input 框实时搜索并发送请求展示下拉列表,没隔一秒发送一次请求 (也可做防抖)

 

防抖和节流的区别
防抖:防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。代码实现重在清零 clearTimeout。
节流:控制流量,单位时间内事件只能触发一次,如果服务器端的限流即 Rate Limit。代码实现重在开锁关锁 timer=timeout; timer=null

 

防抖和节流的代码实现

防抖的实现

/**
* 防抖(debounce)防止抖动:触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。
* @param {*} func 调用用的函数,function
* @param {*} wait 等待的时间,单位ms
* @param {*} immediate 当immediate为true时,第一次调用该函数的时候,就调用func函数;false表示超时之后再调用
*/
export function debounce(func, wait, immediate) {
let timer;
// 通过闭包缓存一个定时器 id
return function () {
// 将 debounce 处理结果当作函数返回
// 触发事件回调时执行这个返回函数
let context = this;
// 把上下文的this对象保存下来,因为下面的apply要使用
let args = arguments;
console.log(context,"90909090")
// ...args:使用es6的rest运算符,把逗号隔开的值序列组合成一个数组:如test(1,2,3,4) ==> args:[1,2,3,4]
// 符合apply(obj,[])
if (timer) clearTimeout(timer);
//关键点 防抖重在清零
// 如果已经设定过定时器就清空上一次的定时器,clearTimeout取消延迟执行的代码块
if (immediate) {
// 如果immediate为true,那么立马调用该函数
var callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
// 过了设定的时间,才执行传过来的函数
} else {
// 开始设定一个新的定时器,定时器结束后执行传入的函数 fn
timer = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
}

 

节流的实现

//这个是在上一个函数上的改进,加强版节流函数 throttle
//如下,新增逻辑在于当前触发时间和上次触发的时间差小于时间间隔时,设立一个新的定时器,相当于把 debounce 代码放在了小于时间间隔部分。
export function throttle(fn, wait) {
let timer = null;
let prev = new Date();
return function () {
let nowTime = new Date();
// 获取当前时间,转换成时间戳,单位毫秒
let context = this;
clearTimeout(timer);
// ------ 新增部分 start ------
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔
// 如果小于,则为本次触发操作设立一个新的定时器
// 定时器时间结束后执行函数 fn
if (nowTime - prev > wait) {
fn.apply(context, arguments);
prev = new Date();
// ------ 新增部分 end ------
} else {
timer = setTimeout(() => {
fn.apply(context, arguments);
}, wait);
}
};
}

 

延伸:
es6 rest argument
es6 引入了rest参数(形式:...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

var fun = (...item)=>{
console.log(item)
}
fun(11,22,33,44,55,66,77,88,99)

 

apply方法
apply:方法能劫持另外一个对象的方法,继承另外一个对象的属性.

Function.apply(obj,args)方法能接收两个参数
obj:这个对象将代替Function类里this对象
args:这个是数组,它将作为参数传给Function(args-->arguments)

用法:将数组作为函数参数
例子:Math.max后面可以接任意个参数,最后返回所有参数中的最大值。
一般这样做,需要取数组里面每个值,毕竟很多传参是接受对象的

function getMax(arr){
var arrLen=arr.length;
for(var i=0,ret=arr[0];i<arrLen;i++){
ret=Math.max(ret,arr[i]);
}
return ret;
}


用apply就简单很多

function getMax2(arr){
return Math.max.apply(null,arr);
}

 

这样看来`...args`,使用es6的rest运算符,把逗号隔开的值序列组合成一个数组:如test(1,2,3,4) ==> args:[1,2,3,4],
`apply`把数组转成对象。这里args是继承的传入函数的参数。

防抖代码,关键就是,对象转换两遍,clearTimeout取消延迟执行的代码块,清零。

其实不管防抖和截流还有很多其他方法,都可以用loadash类库去实现,不用自己苦哈哈的写。

posted @ 2021-04-14 17:41  优前程  阅读(1881)  评论(0编辑  收藏  举报