节流、防抖探究
以下场景往往由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。
-
window对象的resize、scroll事件
-
拖拽时的mousemove事件
-
文字输入、自动完成的keyup事件
-
用户快速click事件
针对这样的需求就出现了debounce
和throttle
两种解决办法。
比如:实现一个原生的拖拽功能(不能用 H5 Drag&Drop API),需要一路监听 mousemove 事件,在回调中获取元素当前位置,然后重置 dom 的位置。如果我们不加以控制,每移动一定像素而触发的回调数量是会非常惊人的,回调中又伴随着 DOM 操作,继而引发浏览器的重排与重绘,性能差的浏览器可能就会直接假死,这样的用户体验是非常糟糕的。我们需要做的是降低触发回调的频率,比如让它 500ms 触发一次,或者 200ms,甚至 100ms,这样的解决方案就是函数节流
。
节流: 函数节流的核心是,让一个函数不要执行得太频繁,减少一些过快的调用。
// 代码片段
const throttle = (fn, timeGap) => {
var last;
// 定时器
var timer;
return function () {
// 保存函数调用时的上下文和参数,传递给 fn
var context = this;
var args = arguments;
// console.log(args, 'args');
var now = +new Date();
// 执行 fn,并重新计时
if (last && now < last + timeGap) {
clearTimeout(timer);
// 保证在当前时间区间结束后,再执行一次 fn
timer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, timeGap)
// 在时间区间的最开始和到达指定间隔的时候执行一次 fn
} else {
last = now;
fn.apply(context, args);
}
};
};
对于浏览器窗口,每做一次 resize 操作,发送一个请求,就需要监听 resize 事件,但是和 mousemove 一样,每缩小(或者放大)一次浏览器,实际上会触发 N 多次的 resize 事件,用节流的话,节流只能保证定时触发,我们一次就好,这就要用去抖。
简单的说,函数去抖就是对于一定时间段的连续的函数调用,只让其执行一次。
// 代码片段
const debounce = (fn, time) => {
let timer = null;
return function () {
// console.log('arguments', args);
clearTimeout(timer);
const args = arguments;
const context = this;
timer = setTimeout(() => {
fn && fn.apply(context, args);
}, time);
};
};
以下是自己在实际项目中遇到的问题:
描述:当单击头像显示个人资料页,双击没有任何反应。
这是上述图片的clickFun函数
var last;
var timer = null;
function clickFun(e) {
const now = new Date().getTime();
if (last) {
clearTimeout(timer);
timer = null;
if (now - last > 400) {
timer = setTimeout(() => {
clickThrottle(e);
}, 400);
}
last = now;
}
else {
timer = setTimeout(() => {
clickThrottle(e);
}, 400);
last = now;
}
}
如果使用去抖的话,在指定时间内可能会多次执行的只会被打包成一次。那如果用户过一段时间再次进行单击/双击操作的话,双击其实还是会进行操作,并不能达到想要的效果,所以这里才用了节流跟定时器的结合(当用户进行点击的时候,在*秒内只能触发一次,这样就可以规避双击的问题)。
总结:
节流throttle :使得一定时间内只触发一次函数。
它和防抖动最大的区别就是,节流函数不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而防抖动只是在最后一次事件后才触发一次函数。
原理是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。
防抖动debounce:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。