防抖
节流
- 使用场景:用于需要执行多次且触发相对平滑的情况,单位时间内事件只能触发一次,如:scroll 事件、mouseover事件、播放事件等。
- 类型:节流函数分为立即执行和非立即执行两种,立即执行的为前缘节流,非立即执行的为延迟节流。
前缘节流
// 前缘节流,首次触发立即生效,后续需要间隔一段时间触发才生效
function throttleImmediateExecution(fn, delay){
let timer = null; // 使用定时器实现
return function (){
let _this = this; // 当前 this 保存,以免后续处理中 this 丢失
if(!timer){
fn.apply(_this, arguments);
timer = setTimeout(function (){
timer = null;
}, delay);
}
}
}
延迟节流
// 延迟节流,首次及后续都需要间隔一段时间触发才生效
function throttleDelayExecution(fn, delay){
let last = Date.now(); // 使用时间戳实现
return function (){
let now = Date.now();
if(now - last >= delay){
fn.apply(this, arguments);
last = now;
}
}
}
可选前缘或延迟节流
// 节流完整版,可选前缘节流或延迟节流,个人感觉这个最舒服
function throttle(fn, delay, isImmediate = true){
// isImmediate 为 true 时使用前缘节流,首次触发会立即执行,为 false 时使用延迟节流,首次触发不会立即执行
let last = Date.now();
return function (){
let now = Date.now();
if(isImmediate){
fn.apply(this, arguments);
isImmediate = false;
last = now;
}
if(now - last >= delay){
fn.apply(this, arguments);
last = now;
}
}
}
防抖
-
使用场景:用于高频触发且有一定停顿的情况,单位时间内事件触发则等待时间会被重置,如:用户在短时间内多次点击登陆、搜索框根据输入的一部分值进行联想搜索(也可以使用节流)、短信验证码、resize等。
-
类型:防抖函数分为立即执行和非立即执行两种,立即执行的为前缘防抖,非立即执行的为延迟防抖。
前缘防抖
// 前缘防抖(定时器版本),在一定时间间隔内的连续触发只执行首次
function debounceImmediateExecution(fn, delay){
let timer = null;
return function (){
let args = [...arguments];
if(!timer){ // 首次触发或间隔 delay 时间后触发,立即执行 fn
fn.apply(this, args);
// 设置定时器
timer = setTimeout(function (){
timer = null;
}, delay);
}else{ // 在间隔时间内触发
// 取消旧的定时器
clearTimeout(timer);
// 设置新的延时定时器
timer = setTimeout(function (){
fn.apply(this, args);
timer = null;
}, delay)
}
}
}
// 前缘防抖(时间戳版本,比使用定时器开销更低),在一定时间间隔内的连续触发只执行首次
function debounceImmediateExecutionPlus(fn, delay){
let last = Date.now();
let first = true; // 是否为首次执行
return function (){
let args = [...arguments];
if(first){ // 首次触发
fn.apply(this, args);
last = Date.now();
first = false;
}else{ // 后续触发
let now = Date.now();
if(now - last >= delay){
fn.apply(this, args);
}
last = now;
}
}
}
延迟防抖
// 延迟防抖,在一定时间间隔内的连续触发只执行最后一次
function debounceDelayExecution(fn, delay){
let timer = null;
return function (){ // 最后将这个闭包函数返回作为包装后的事件监听函数
clearTimeout(timer); // 取消旧的定时器
let _this = this; // this 指向监听的节点,此处不保存的话到了定时器回调函数中 this 就会变为 window
let args = [...arguments]; // 事件监听函数的参数
// 重置定时器
timer = setTimeout(function (){
fn.apply(_this, args);
}, delay)
}
}
// 个人理解:延迟防抖最好用定时器实现,因为需要在满足某个条件后,让 fn 在经过 delay 时间后执行
可选前缘或延迟防抖
// 防抖完整版(相当于前缘防抖和延迟防抖都使用定时器实现时的结合),可选前缘防抖(默认)或者延迟防抖
function debounce(fn, delay, isImmediate = true){
let timer = null;
return function (){
let args = [...arguments];
let _this = this; // 保存 this 供后续操作中使用
if(timer){ // 已有定时器时,定时器需要重置,代表对中途连续触发的处理
clearTimeout(timer); // 取消旧的定时器
if(isImmediate){ // 使用前缘防抖时
timer = setTimeout(function (){ // 创建新的定时器,用于时间延迟
timer = null; // 执行后置空
}, delay);
}else{ // 使用延迟防抖时
timer = setTimeout(function (){ // 创建新的定时器
fn.apply(_this, args);
timer = null; // 执行后置空
}, delay);
}
}else{ // 没有定时器时,代表对首次触发或者间隔时间>=delay时的触发进行处理
if(isImmediate){ // 使用前缘防抖时
fn.apply(this, args)
timer = setTimeout(function (){ // 创建新的定时器,用于时间延迟
timer = null;
}, delay);
}else{ // 使用延迟防抖时
timer = setTimeout(function (){ // 创建新的定时器
fn.apply(_this, args);
timer = null; // 执行后置空
}, delay);
}
}
}
}
在微信小程序中使用(TS方式)
export function throttle(fn: (...args: any[]) => any, dealy: number) {
let last = new Date().getTime();
// 第一个参数指定this类型,让fn.apply(this, args)这里使用this时不会TS报错
return function (this: any, ...args: any[]) {
let now = Date.now();
if (now - last >= dealy) {
fn.apply(this, args);
last = now;
}
};
}
Page({
onTouchMove: throttle(function (this: any, e: WechatMiniprogram.TouchEvent) {
// 引入入参的第一个参数声明了this类型为any,所以此时this类型为any
// 因为 throttle 内部是通过`fn.apply(this, args);`调用的回调方法,所以this已经被重新设置为调用处的this了,也就是this实际被重置为当前的page对象
// 更期望的方式是: 写 this.xxx 时,能给出代码提示,但因为this被定义成了any,无法给出有效ts提示,但能防止ts报错
// 为啥不给this指定为实际的Page类型?因为暂未发现有简单的方式获取Page对象的实际类型
console.log(this)
// 你的业务逻辑代码
}, 50)
})
<view class="absolute top-0 w-full transition-transform" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" style="{{translateYStyle}}">
</view>
扩展
箭头函数与普通函数的区别详解_箭头函数与普通函数有哪些区别-CSDN博客
聊一聊Typescript中与this相关的类型定义 - 掘金 (juejin.cn)
参考资料
小程序开发中使用节流函数throttle的正确方式_loadsh throttle 不起作用-CSDN博客