防抖与节流
前言
防抖与节流是前端性能优化中很重要的一环,防抖可以用来减少页面请求数量,节流则可以用于减少监听事件回调函数的触发次数。防抖与节流都是用来减少页面开销的方法。
虽然二者对性能提升有很大的帮助,但是**对用户体验而言,对点击事件做防抖或节流并不是一个好的解决方案,而更像是一种无奈之举,尽量不要使用。**我们可以对一些函数做一下防抖,但是尽量不要对按钮做,因为防抖的本质确实会给我们的用户交互带来体验上的“卡顿”。
防抖
防抖是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
举一个现实中的例子:商场里的感应扶梯,当没人的时候为了节能会降低运行速度。就是在检测到没人(触发事件)后 n 秒后触发减速(执行函数),如果n秒内又有人(又触发事件),则不减速(不执行函数)并重新计时。
在开发中主要用于在一定时间内不触发特定动作时再触发回调函数。比如输入框的自动搜索,当用户输入结束后再进行搜索。可以使用防抖避免用户每输入一个字符就进行一次搜索。
实现也很简单:
late Timer timer;
void debounce(Function function) {
timer?.cancel();
timer = Timer(
Duration(milliseconds: 1000),
() {
///do something
},
);
}
节流
节流是指在连续触发事件时,在 n 秒中只执行一次函数。它会稀释函数的执行频率。和防抖不同的是它会立即执行,然后在之后的n秒内不再响应。也就是规定了函数在单位事件内最多只能被触发一次。现实中常用的一个节流方法就是排队时限流。
代码实现如下:
void throttle() {
if (timer != null) {
return;
}
///do something
timer = Timer(
Duration(milliseconds: 1000),
() {
timer = null;
},
);
}
工具类实现防抖节流
抽取工具类如下:
///默认时长
const _defaultDuration = Duration(milliseconds: 380);
enum FilterType {
debounce,
throttle,
}
typedef VoidFunction = void Function();
class EventFilter {
static Map<String, Timer> _wrappers = {};
///防抖
static VoidFunction debounce(String sign, function,
{Duration duration = _defaultDuration}) {
return () {
execute(sign, function,
duration: duration, filterType: FilterType.debounce);
};
}
///节流
static VoidFunction throttle(String sign, function,
{Duration duration = _defaultDuration}) {
return () {
execute(sign, function,
duration: duration, filterType: FilterType.throttle);
};
}
static void execute(String sign, function,
{Duration duration = _defaultDuration,
FilterType filterType = FilterType.debounce}) {
switch (filterType) {
case FilterType.debounce:
_wrappers[sign]?.cancel();
break;
case FilterType.throttle:
if (_wrappers.containsKey(sign)) {
return;
} else {
function.call();
}
break;
}
_wrappers[sign] = Timer(
duration,
() {
if (filterType == FilterType.debounce) {
function.call();
}
_wrappers[sign]?.cancel();
_wrappers.remove(sign);
},
);
}
///在state的dispose方法里移除Timer
static void remove(String sign) {
if (_wrappers.containsKey(sign)) {
_wrappers[sign]?.cancel();
_wrappers.remove(sign);
}
}
///移除所有Timer
static void clear() {
_wrappers.forEach((key, value) {
remove(key);
});
_wrappers.clear();
}
static void removeState(String hashString) {
_wrappers.removeWhere((key, value) => key.startsWith(hashString));
}
}
///State扩展类
///每个State所有的防抖和节流都带有同样的前缀
///方便管理和统一释放资源
extension EventFilterExtension on State {
stateFilter(String sign, Function function,
{Duration duration = _defaultDuration,
FilterType filterType = FilterType.debounce}) {
EventFilter.execute("${this.hashCode.toString()}$sign", function,
duration: duration, filterType: filterType);
}
clearStateFilter() {
EventFilter.removeState(this.hashCode.toString());
}
}
使用方法如下:
@override
void dispose() {
EventFilter.remove("tag");
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: EventFilter.throttle(
"tag",
() {
print("------------");
},
),
child: Text("按钮"),
),
);
}
总结
- 防抖:n秒后执行;
- 节流:n秒内最多执行一次。
在实际开发中,合理使用防抖和节流会减少性能负担,提升用户体验。但是要避免滥用,个人建议主要用在函数防抖上。
大多数人都喜欢去做点击事件的防抖。我个人是非常不推荐去给点击事件做防抖的。因为用户在大多数情况下是不太会无聊到去疯狂点击按钮的。大多数情况下出现连续点击的情况并不是因为用户真的“手抖”,而是按钮响应不及时或者反馈不明显。此时如果粗暴的使用防抖,对用户的体验不仅没有任何提升,反而会加重不好的体验。
所以,个人认为:当你想要对一个按钮使用防抖时,一定要明白这是下下策。在使用之前,你一定要思考一下有没有更好的交互方案去解决问题,比如加上更显著的点击反馈效果,或者视情况加入过度、转场效果等。当然,如果能做更深入的优化,提升流畅度是最好的。
对用户体验而言,按钮的防抖并不是一个好的解决方案,而更像是一种无奈之举。当你想要对一个按钮使用防抖时,首先要思考溯源,发现问题的本质,优化才是第一选择。 为了提升性能对一些函数做防抖和节流是没问题的,但是尽量不要对按钮做,因为防抖本质上确实会给我们的用户交互带来体验上的“卡顿”——因为它确实会带来“无响应”。