项目中,反复的点击按钮或连续的触发事件,会导致响应跟不上触发频率或资源反复请求浪费的问题(例:ajax访问)
;这种问题在项目中很常见,处理不当或放任不管就会容易引起浏览器卡顿的不好用户体验的问题;接口重复多次请求同一个数据接口也会资源浪费的问题。
这里介绍俩个性能优化:防抖(debounce
),节流(throttle
)
防抖(debounce
)与节流(throttle
)都是优化事件的触发机制,减少事件频繁的触发,二者的区别是:
-
防抖(
debounce
)是在连续触发时:最后一次执行事件- 主要应用应用场景:
- 搜索框输入实时搜索:当用户在搜索框中快速输入时,不必每次输入都发起搜索请求,而是在输入停止一段时间(例如 500 毫秒)后再进行搜索,避免频繁的请求。
- 窗口大小调整:在响应窗口大小变化的事件中,防抖可以避免在用户调整窗口大小时频繁触发重新布局或重新计算样式的操作。
- 文本编辑自动保存:在用户编辑文本时,不是每次修改都立即保存,而是在停止编辑一段时间后自动保存,减少保存操作的次数。
- 主要应用应用场景:
-
节流(
throttle
)是在连续触发时:间隔的去触发事件- 主要应用应用场景:
- 滚动加载数据:当页面滚动到底部时加载更多数据,使用节流可以控制加载的频率,避免在快速滚动时频繁加载。
- 鼠标移动事件:例如在一个需要根据鼠标移动实时更新某些元素位置或状态的场景中,节流可以限制更新的频率,保证性能。
- 高频点击操作:如点赞按钮,防止用户在短时间内快速多次点击而导致重复的操作被执行。
例如,一个电商网站的商品列表页面,当用户滚动时,通过节流控制加载更多商品的频率,既能及时提供新的内容,又不会因为频繁加载影响性能。
- 主要应用应用场景:
防抖 - debounce
主要应用的场景是:keyup
的监听,input的频繁输入的监听事件的流程:
const inputSearch = document.getElementById('input');
let timer = null;
inputSearch.addEventListener('keyup', function() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
console.log(inputSearch.value); // 触发change事件打印输出input的value
timer = null; // 清空定时器
}, 500);
});
将上面的封装成debounce
方法的使用如下:
// 防抖的通用方法
function debounce(fun, delay = 500) {
let timer = null; // 在闭包中
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
// fun();
fun.bind(this, arguments); // this的指向
timer = null;
}, delay);
}
}
// input的keyup使用节流的方法输出
inputSearch.addEventListener('keyup', debounce(function(event) {
console.log(event.value); //
}));
节流 - throttle
与上方的原理相同,防抖面对timer是清除重新赋值一个新的,节流是面对有timer,直接返回等待定时器执行完;
// 节流的通用方法
function throttle(fun, delay = 500) {
let timer = null;
return function() {
if (timer) {
return;
}
timer = setInterval(() => {
// fun();
fun.bind(this, arguments);
timer = null
}, delay);
}
}
// input的keyup使用节流的方法输出
inputSearch.addEventListener('keyup', throttle(function(event) {
console.log(event.value); //
}));
使用节流的方法,第一次点击触发,然后在相隔的时间再次触发才生效,更改如下:
function throttle(func, delay) {
let lastClicked = 0;
let isFirstClick = true;
return function() {
const now = Date.now();
if (isFirstClick || now - lastClicked >= delay) {
func.apply(this, arguments);
lastClicked = now;
isFirstClick = false;
}
};
}
这里有完整的介绍防抖与节流的多种方法引用:点击进入该作者的文章阅读
使用注册Vue.directive全局指令,并封装一个js的文件,直接引用到main.js中,可直接的使用
// directives.js
import Vue from 'vue';
const throttleDirective = {
bind(el, binding, vnode) {
let timer;
let lastClicked = 0;
let isFirstClick = true;
const delay = parseInt(binding.modifiers.throttle) || 200; // 从修饰符中获取节流的延迟时间
el.addEventListener('click', function(event) {
const now = Date.now();
if (isFirstClick || (now - lastClicked >= delay && timer === null)) {
vnode.context[binding.expression](event); // 调用组件中的方法
lastClicked = now;
isFirstClick = false;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
timer = null;
}, delay);
}
});
}
}
const debounceDirective = {
bind(el, binding, vnode) {
let timer;
el.addEventListener('click', function(event) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
vnode.context[binding.expression](event); // 调用组件中的方法
}, parseInt(binding.modifiers.debounce) || 200); // 从修饰符中获取防抖的延迟时间
});
}
}
Vue.directive('throttle', throttleDirective);
Vue.directive('debounce', debounceDirective);
导入到main.js文件中
import './directives';
使用示例:
<template>
<div>
<button @click.throttle.500="throttleClick">节流点击(500 毫秒)</button>
<button @click.debounce.300="debounceClick">防抖点击(300 毫秒)</button>
</div>
</template>
<script>
export default {
methods: {
throttleClick(event) {
console.log('节流点击事件触发');
},
debounceClick(event) {
console.log('防抖点击事件触发');
}
}
}
</script>
在上述示例中,当点击“节流点击”按钮时,第一次点击会立即触发,之后每隔 500 毫秒才会再次触发 throttleClick 方法。而点击“防抖点击”按钮时,在 300 毫秒内的多次点击只会触发一次 debounceClick 方法。
注:但Vue.directive指令在小程序端不兼容,只能使用以下方式
新建一个directives.js文件
// directives.js
// 节流点击事件使用,主要防止一直点击,反复触发多次,然后点击
export function throttle(func, delay) {
let lastCall = 0;
return function (...args) {
const now = new Date().getTime();
if (now - lastCall >= delay) {
func.apply(this, args);
lastCall = now;
}
};
}
// debounce节流最后一次触发, 主要用于搜索输入的之类场景
export function debounce(func, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
在vue组件中的引用
<template>
<button @click="handleThrottleClick">节流点击</button>
<button @click="handleDebounceClick">防抖点击</button>
</template>
<script>
import { throttle, debounce } from '@/utils/directives';
export default {
methods: {
handleThrottleClick: throttle(() => {
// 这里写点击事件的处理逻辑
console.log('点击事件被触发');
}, 1000),
handleDebounceClick: debounce(() => {
// 这里写点击事件的处理逻辑
console.log('点击事件被触发');
}, 1000),
}
}
</script>