逐步理解js防抖
先抛开"防抖","节流"这两个名词。
先看一个需求场景,在下图中点击加漫游速度,弹窗提示,2秒后消失。此时,如果疯狂点击加,那么速度提示弹窗将会疯狂的显示隐藏。但是我们想要的效果不是这样。
需求
- 1.正常点击加减按钮,显示速度提示弹窗,2秒后隐藏弹窗
- 2.连续点击加减按钮(间隔时间短),显示速度提示弹窗,操作停止后,2秒后隐藏弹窗
解决思路
- 1.速度改变时,执行显示弹窗,定义setTimeOut定时器,2秒后执行隐藏弹窗
- 2.正常情况,用户只点击一次,以上代码正常,如果用户点击频繁,代码1会多次执行, 这时需要clearTimeout清空定时器
- 3.clearTimeout清空定时器,原本定时器在2秒后执行,这时清空就会取消原本定义的2秒后执行,只会执行最后一次的执行
let speedChangeTimer;
cameraWalkOperator.bindChangeSpeedCallback(() => {
console.log("速度改变");
this.setState({
tipSpeed: true,
});
clearTimeout(speedChangeTimer);
speedChangeTimer = setTimeout(() => {
this.setState({
tipSpeed: false,
});
}, 2000);
let speed = cameraWalkOperator.getSpeed();
this.setState({
speedValue: speed,
});
});
防抖
对以上代码进行封装
// 这其实是个闭包函数
function debounce(func, wait) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(function () {
func();
}, wait)
}
}
闭包: 如果想让一个函数执行完后,函数内的某个变量(timer)仍旧保留,就可以使用闭。
把要保存的变量在父作用域声明,其他的语句放到子作用域里,并且作为一个function返回,所以闭包可以理解为分离变量
网上版本
function debounce(method,delay) {
var timer=null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
method.apply(context,args);
},delay);
}
}
想理解这里的this arguments apply; 请看我的这篇文章 juejin.cn/post/684490…
注意
- 查看代码可知返回的是一个函数,没有执行
- 使用时一般绑定在监听上document.addEventListener('mousemove', debounce)
- 如果我们只是普通的使用,没有用dom的这种绑定事件监听,那么得加 () 自运行
- 如下图所示,这个防抖其实是无效的,因为每次执行函数时,debounce都重新执行了一遍,已经不是同一个了,是不同的。
想理解addEventListener绑定事件原理。请看我的这篇文章
定义
定义: 策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。
深入防抖与节流
防抖(debounce)
防抖 — 指触发事件后,就是把触发非常频繁的事件合并成一次去执行。
即在指定时间内只执行一次回调函数,如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算。
生活中的例子
乘车刷卡的情景,只要乘客不断地在刷卡,司机师傅就不能开车,乘客刷卡完毕之后,司机会等待几分钟,确定乘客坐稳再开车。如果司机在最后等待的时间内又有新的乘客上车,那么司机等乘客刷卡完毕之后,还要再等待一会,等待所有乘客坐稳再开车。
代码实现
function debounce(fn, delay) {
// 声明定时器
let timer = null;
// 利用闭包的形式来保存定时器
return function () {
let _this = this;
let args = arguments
clearTimeout(timer); // 再次调用防抖函数则先清空定时器
timer = setTimeout(function () {
fn.apply(_this, args); // 声明定时器(如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算)
}, delay);
}
}
function down () {
console.log('down');
}
elementHtml.addEventListener('click',debounce(dj,1000))
// 1.在 click 事件上绑定处理函数,这时 debounce 函数会立即调用,实际上绑定的函数的 debounce 函数内部返回的函数。
// 2.每一次事件被触发,都会清除当前的 timer 然后重新设置超时调用。
// 3.只有在最后一次触发事件,才能在 delay 时间后执行。
节流(throttle)
节流 — 指频繁触发事件时,只会在指定的时间段内执行事件回调。
即触发事件间隔大于等于指定的时间才会执行回调函数。
区别:防抖动和节流的本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行
生活中的例子
生活中的水龙头,拧紧水龙头到某种程度会发现,每隔一段时间,就会有水滴流出。
代码实现
定时器版-闭包
function throttle (fn, delay) {
// 声明定时器
let timer = null;
// 利用闭包的形式来保存定时器
return function () {
let _this = this;
let args = arguments;
if (!timer) { // 如果没有定时器则设置定时器(使用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,所以当最后一次停止触发后,还会再执行一次函数。)
timer = setTimeout(function () {
fn.apply(_this, args);
timer = null; // 执行完后清空定时器
}, delay);
}
}
}
时间戳版-闭包
function throttle (fn, delay) {
// 声明前一个时间戳
let prev = Date.now();
return function () {
let _this = this;
let args = arguments;
// 声明当前时间戳
let now = Date.now();
if (now - prev >= delay) { // 如果时间戳的差大于等于时间间隔,则执行传入的函数(使用时间戳的节流函数会在第一次触发事件时立即执行,以后每过 delay 秒之后才执行一次,并且最后一次触发事件不会被执行)
fn.apply(_this, args);
prev = Date.now(); // 执行完后设置前一个时间戳
}
}
}
应用场景对比
防抖
- 1.每次 resize/scroll 触发统计事件
- 2.文本输入的验证,连续输入文字后发送Ajax请求进行验证,验证一次就好
节流
- 1.DOM元素的拖拽功能实现
- 2.搜索联想
- 3.计算鼠标移动的距离
- 4.canvas模拟画布功能
- 5.射击游戏的mousedown/keydown事件-单位时间内只能发射一颗子弹
- 6.监听滚动事件判断是否到页面底部自动加载更多
总结
函数防抖:将几次操作合并为一次操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。