1.防抖
理解
- 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
原理
- 每次调用返回的新函数时,都会清除之前设置的定时器。
- 然后重新设置一个定时器,在延迟时间后执行传入的函数。
场景
- 防抖的应用场景很多:
- 输入框中频繁的输入内容,搜索或者提交信息;
- 频繁的点击按钮,触发某个事件;
- 监听浏览器滚动事件,完成某些特定操作;
- 用户缩放浏览器的resize事件;
举个例子
vue中代码
<template>
<div>
<input type="text" v-model="searchQuery" placeholder="Enter your search query...">
<div>
<h3>Search Results:</h3>
<ul>
<li v-for="result in searchResults" :key="result.id">{{ result.name }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
searchQuery: '',
searchResults: []
};
},
methods: {
// 定义防抖函数
debounce(func, delay) {
let timerId;
return function(...args) {
clearTimeout(timerId);
timerId = setTimeout(() => {
// 这里的apply this也很重要
// 确保了search方法内部的 `this` 指向当前 Vue 组件实例,
// 从而可以正确地访问组件的数据、计算属性、方法等
func.apply(this, args);
}, delay);
};
},
// 模拟搜索函数
search(query) {
// 这里可以触发搜索操作,比如发送 AJAX 请求等
console.log(`Searching for: ${query}`);
// 模拟搜索结果
this.searchResults = [
{ id: 1, name: 'Result 1' },
{ id: 2, name: 'Result 2' },
{ id: 3, name: 'Result 3' }
];
}
},
created() {
// 创建防抖搜索函数,延迟时间为500毫秒
// ?????这个地方非常的重要 一定要使用的是这个debounce的返回函数?????
this.debouncedSearch = this.debounce(this.search, 500);
},
watch: {
// 监听搜索关键字变化,并调用防抖搜索函数
searchQuery(newQuery) {
this.debouncedSearch(newQuery);
}
}
};
</script>
优化一:优化立即执行效果(第一次立即执行)
<template>
<div>
<input type="text" v-model="searchQuery" placeholder="Enter your search query...">
<div>
<h3>Search Results:</h3>
<ul>
<li v-for="result in searchResults" :key="result.id">{{ result.name }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
searchQuery: '',
searchResults: []
};
},
methods: {
// 定义优化后的防抖函数
debounce(func, delay, immediate) {
let timerId;
let isFirstCall = true;
return function(...args) {
const context = this;
if (immediate && isFirstCall) {
func.apply(context, args);
isFirstCall = false;
}
clearTimeout(timerId);
timerId = setTimeout(() => {
if (!immediate || !isFirstCall) {
func.apply(context, args);
}
isFirstCall = true;
}, delay);
};
},
// 模拟搜索函数
search(query) {
// 这里可以触发搜索操作,比如发送 AJAX 请求等
console.log(`Searching for: ${query}`);
// 模拟搜索结果
this.searchResults = [
{ id: 1, name: 'Result 1' },
{ id: 2, name: 'Result 2' },
{ id: 3, name: 'Result 3' }
];
}
},
created() {
// 创建优化后的防抖搜索函数,延迟时间为500毫秒,第一次触发立即执行
this.debouncedSearch = this.debounce(this.search, 500, true);
},
watch: {
// 监听搜索关键字变化,并调用防抖搜索函数
searchQuery(newQuery) {
this.debouncedSearch(newQuery);
}
}
};
</script>
优化二:优化取消操作(增加取消功能)
<template>
<div>
<input type="text" v-model="searchQuery" placeholder="Enter your search query...">
<button @click="cancelSearch">Cancel</button>
<div>
<h3>Search Results:</h3>
<ul>
<li v-for="result in searchResults" :key="result.id">{{ result.name }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
searchQuery: '',
searchResults: [],
debouncedSearch: null // 保存防抖函数
};
},
methods: {
// 定义优化后的防抖函数
debounce(func, delay, immediate) {
let timerId;
let isFirstCall = true;
function debounced(...args) {
const context = this;
if (immediate && isFirstCall) {
func.apply(context, args);
isFirstCall = false;
}
clearTimeout(timerId);
timerId = setTimeout(() => {
if (!immediate || !isFirstCall) {
func.apply(context, args);
}
isFirstCall = true;
}, delay);
}
// 取消函数
debounced.cancel = function() {
clearTimeout(timerId);
isFirstCall = true;
};
return debounced;
},
// 模拟搜索函数
search(query) {
// 这里可以触发搜索操作,比如发送 AJAX 请求等
console.log(`Searching for: ${query}`);
// 模拟搜索结果
this.searchResults = [
{ id: 1, name: 'Result 1' },
{ id: 2, name: 'Result 2' },
{ id: 3, name: 'Result 3' }
];
},
// 取消搜索函数
cancelSearch() {
if (this.debouncedSearch) {
this.debouncedSearch.cancel();
}
}
},
created() {
// 创建优化后的防抖搜索函数,延迟时间为500毫秒,第一次触发立即执行
this.debouncedSearch = this.debounce(this.search, 500, true);
},
watch: {
// 监听搜索关键字变化,并调用防抖搜索函数
searchQuery(newQuery) {
this.debouncedSearch(newQuery);
}
}
};
</script>
2.节流
理解
- 当事件触发时,会执行这个事件的响应函数;
- 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数;
- 不管在这个中间有多少次触发这个事件,执行函数的频繁总是固定的;
- 节流(throttling)是一种限制函数的执行频率的技术,它确保函数在一定时间间隔内最多执行一次
原理
- 在每次调用节流函数返回的新函数时,首先获取当前时间。
- 如果距离上次执行函数的时间已经超过设定的 delay,直接执行函数,并更新上次执行时间为当前时间。
- 如果距离上次执行函数的时间还没到 delay,那么先判断是否已经有定时器在等待执行。
- 如果没有,则设置一个定时器,在延迟结束后执行函数,并在执行后更新上次执行时间。
- 如果已经有定时器在等待执行,就不再设置新的定时器。
代码
<template>
<div>
<div ref="scrollContainer" style="height: 500px; overflow-y: scroll;" @scroll="handleScroll"></div>
<div :style="{ marginTop: marginTop + 'px' }">滚动元素</div>
</div>
</template>
<script>
export default {
data() {
return {
marginTop: 0,
handleScrollThrottled: null // 节流函数
};
},
mounted() {
// 创建节流函数,限制滚动处理函数的执行频率
this.handleScrollThrottled = this.throttle(this.handleScroll, 200);
// 监听滚动事件,调用节流函数处理滚动事件
this.$refs.scrollContainer.addEventListener('scroll', this.handleScrollThrottled);
},
beforeDestroy() {
// 组件销毁时移除滚动事件监听
this.$refs.scrollContainer.removeEventListener('scroll', this.handleScrollThrottled);
},
methods: {
// 节流函数
throttle(func, delay) {
let lastExecTime = 0;
let timerId;
return function(...args) {
const currentTime = Date.now();
// 如果距离上次执行函数的时间已经超过 delay,直接执行函数
if (currentTime - lastExecTime >= delay) {
func.apply(this, args);
lastExecTime = currentTime;
} else {
// 如果还没到延迟时间,则设置一个定时器,在延迟结束后执行函数
if (!timerId) {
timerId = setTimeout(() => {
func.apply(this, args);
lastExecTime = Date.now();
timerId = null;
}, delay - (currentTime - lastExecTime));
}
}
};
},
// 处理滚动事件
handleScroll(event) {
// 更新页面元素的位置,例如根据滚动条位置来调整元素的上边距
this.marginTop = event.target.scrollTop;
}
}
};
</script>
优化二:优化取消操作(增加取消功能)
export default {
data() {
return {
marginTop: 0,
handleScrollThrottled: null, // Throttled scroll handling function
};
},
mounted() {
this.createThrottledScrollHandler();
this.addScrollEventListener();
},
beforeDestroy() {
this.removeScrollEventListener();
this.cancelThrottledScrollHandler();
},
methods: {
createThrottledScrollHandler() {
this.handleScrollThrottled = this.throttle(this.handleScroll, 200);
},
throttle(func, delay) {
let lastExecTime = 0;
let timerId;
function throttled(...args) {
const currentTime = Date.now();
if (currentTime - lastExecTime >= delay) {
func.apply(this, args);
lastExecTime = currentTime;
} else {
if (!timerId) {
timerId = setTimeout(() => {
func.apply(this, args);
lastExecTime = Date.now();
timerId = null;
}, delay - (currentTime - lastExecTime));
}
}
}
throttled.cancel = function() {
clearTimeout(timerId);
timerId = null;
};
return throttled;
},
addScrollEventListener() {
this.$refs.scrollContainer.addEventListener('scroll', this.handleScrollThrottled);
},
removeScrollEventListener() {
this.$refs.scrollContainer.removeEventListener('scroll', this.handleScrollThrottled);
},
cancelThrottledScrollHandler() {
if (this.handleScrollThrottled) {
this.handleScrollThrottled.cancel();
}
},
handleScroll(event) {
this.marginTop = event.target.scrollTop;
}
}
};
综上所述
防抖(Debouncing)和节流(Throttling)都是用于控制函数执行频率的技术,它们的区别在于执行函数的时机不同:
- 防抖(Debouncing): 在防抖技术中,函数调用会被延迟,直到一定时间内没有新的函数调用发生。如果在延迟时间内发生了新的函数调用,则之前的延迟调用会被取消,新的延迟调用会被设置。这意味着只有在连续函数调用停止一段时间后,函数才会真正执行。防抖常用于需要等待一段时间后再执行的场景,例如用户输入搜索框时,不立即发送请求,而是等待用户停止输入一段时间后再发送请求。
- 节流(Throttling): 在节流技术中,函数会在一定时间间隔内最多执行一次。如果在时间间隔内多次调用了函数,则只有第一次函数调用会被执行,而其他的调用会被忽略。这意味着函数执行的频率被限制在一定的时间间隔内。节流常用于需要控制函数执行频率的场景,例如滚动事件、窗口大小调整事件等。
综上所述,防抖和节流都可以用于减少函数的执行次数,提高性能和用户体验。它们的选择取决于具体的业务需求和使用场景。如果需要等待一段时间后再执行函数,则选择防抖;如果需要在一定时间间隔内限制函数的执行频率,则选择节流。