节流(Throttle)是一种常用的性能优化技术,用于限制函数的执行频率,确保在一定时间内只执行一次。它常用于处理浏览器事件(如滚动、窗口调整大小、鼠标移动等),以避免因事件触发过于频繁而导致的性能问题。
以下是手写节流函数的实现方法,以及一些常见的使用场景和注意事项。
节流函数的实现
节流函数的核心思想是:在指定的时间间隔内,只允许函数执行一次。以下是两种常见的节流实现方式
1. 时间戳版本(基于时间间隔)
ini
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function () {
const context = this;
const args = arguments;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function () {
if (Date.now() - lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
2. 定时器版本(基于固定时间间隔)
ini
function throttle(func, limit) {
let inThrottle;
return function () {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
使用示例
1. 滚动事件
javascript
window.addEventListener('scroll', throttle(function () {
console.log('Scrolling...');
}, 100));
2. 窗口调整大小
javascript
window.addEventListener('resize', throttle(function () {
console.log('Resizing...');
}, 200));
注意事项
-
参数传递:
- 节流函数需要正确处理原函数的上下文(
this
)和参数(arguments
),确保它们能够正确传递。
- 节流函数需要正确处理原函数的上下文(
-
性能优化:
- 节流函数本身也会影响性能,因此需要合理设置时间间隔。
-
销毁定时器:
- 如果使用了定时器版本,记得在组件销毁时清除定时器,避免内存泄漏。
完整代码示例
以下是一个完整的节流函数实现,结合了时间戳和定时器的优点:
javascript
function throttle(func, limit) {
let inThrottle;
let lastRan;
return function () {
const args = arguments;
const context = this;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(inThrottle);
inThrottle = setTimeout(function () {
if (Date.now() - lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
// 使用示例
window.addEventListener('scroll', throttle(function () {
console.log('Scrolling...');
}, 100));
防抖(Debounce)是一种常用的性能优化技术,用于限制函数的执行频率。与节流(Throttle)不同,防抖的核心思想是:在指定的时间间隔内,只有最后一次触发事件时才执行函数。如果在时间间隔内再次触发事件,则重新计时。
防抖常用于以下场景:
- 输入框的搜索建议:避免用户输入时频繁触发搜索请求。
- 按钮点击:防止用户快速多次点击导致多次提交。
- 窗口调整大小:避免频繁触发布局调整。
以下是防抖函数的实现方法以及具体案例。
防抖函数的实现
防抖函数的实现相对简单,核心是利用 setTimeout
和 clearTimeout
来控制函数的执行。
基本实现
javascript
function debounce(func, delay) {
let timeoutId;
return function (...args) {
const context = this;
clearTimeout(timeoutId); // 清除之前的定时器
timeoutId = setTimeout(() => {
func.apply(context, args); // 延迟后执行函数
}, delay);
};
}
实现思路
- 每次触发事件时,清除之前的定时器。
- 设置一个新的定时器,延迟指定时间后执行目标函数。
- 如果在延迟时间内再次触发事件,则重新计时。
具体案例:输入框搜索建议
假设我们有一个输入框,用户输入时需要触发搜索建议。为了避免频繁触发搜索请求,可以使用防抖技术。
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Debounce Example</title>
</head>
<body>
<input type="text" id="search-input" placeholder="Search...">
<div id="suggestions"></div>
<script>
// 防抖函数
function debounce(func, delay) {
let timeoutId;
return function (...args) {
const context = this;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
// 搜索建议函数
function searchSuggestions(query) {
console.log(`Searching for: ${query}`);
const suggestions = document.getElementById('suggestions');
suggestions.innerHTML = `<p>Showing suggestions for: ${query}</p>`;
}
// 获取输入框
const input = document.getElementById('search-input');
// 使用防抖绑定输入事件
input.addEventListener('input', debounce((event) => {
searchSuggestions(event.target.value);
}, 500)); // 延迟 500 毫秒
</script>
</body>
</html>
防抖的高级用法
1. 立即执行与延迟执行
防抖函数可以配置是否在第一次触发时立即执行,而不是延迟执行。以下是改进版的防抖函数:
ini
function debounce(func, delay, immediate = false) {
let timeoutId;
return function (...args) {
const context = this;
const callNow = immediate && !timeoutId; // 是否立即执行
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
if (!immediate) {
func.apply(context, args); // 延迟执行
}
timeoutId = null;
}, delay);
if (callNow) {
func.apply(context, args); // 立即执行
}
};
}
使用示例
less
// 延迟执行
input.addEventListener('input', debounce((event) => {
searchSuggestions(event.target.value);
}, 500));
// 立即执行
input.addEventListener('input', debounce((event) => {
searchSuggestions(event.target.value);
}, 500, true));
与防抖的区别
节流和防抖都是用于限制函数执行频率的技术,但它们的实现和用途有所不同:
-
节流(Throttle) :
- 在指定的时间间隔内,确保函数只执行一次。
- 适用于需要定期触发的场景(如滚动、窗口调整大小)。
-
防抖(Debounce) :
- 在指定的时间间隔内,只有最后一次触发时才执行函数。
- 适用于需要延迟执行的场景(如输入框的搜索建议、按钮点击)
在 Vue 中使用:
javascript
<template>
<input type="text" v-model="inputValue" @input="debouncedInput" />
</template>
export default {
data() {
return {
inputValue: ""
};
},
methods: {
handleInput() {
console.log("Debounced input:", this.inputValue);
}
},
mounted() {
this.debouncedInput = debounce(this.handleInput, 500);
}
};
2. 使用 Lodash 的 debounce throttle
Lodash 是一个常用的 JavaScript 工具库,提供了现成的防抖函数
安装 Lodash:
npm install lodash
使用 debounce:
javascript
import { debounce } from "lodash";
export default {
data() {
return {
inputValue: ""
};
},
methods: {
handleInput() {
console.log("Debounced input:", this.inputValue);
}
},
mounted() {
this.debouncedInput = debounce(this.handleInput, 500);
}
};
模板绑定:
lua
<input type="text" v-model="inputValue" @input="debouncedInput" />
使用 throttle:
xml
<template>
<div>
<h1>Lodash 节流示例</h1>
<p>滚动页面查看节流效果</p>
</div>
</template>
<script>
import { throttle } from 'lodash';
export default {
name: 'LodashThrottleExample'
methods: {
handleScroll() {
console.log('滚动事件触发');
// 在这里添加你的滚动逻辑
}
},
mounted() {
// 使用 Lodash 的 throttle 函数
this.throttledScroll = throttle(this.handleScroll, 200);
window.addEventListener('scroll', this.throttledScroll);
},
beforeDestroy() {
window.removeEventListener('scroll', this.throttledScroll);
}
};
</script>
xml
<template>
<div>
<h1>Lodash 节流示例</h1>
<p>滚动页面查看节流效果</p>
</div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue';
import { throttle } from 'lodash';
const handleScroll = () => {
console.log('滚动事件触发');
// 在这里添加你的滚动逻辑
};
const throttledScroll = throttle(handleScroll, 200);
onMounted(() => {
window.addEventListener('scroll', throttledScroll);
});
onUnmounted(() => {
window.removeEventListener('scroll', throttledScroll);
});
</script>
3. 使用 Vue 组合式 API 封装防抖
Vue 3 的组合式 API 提供了更灵活的方式封装防抖逻辑。
封装 useDebounce
:
ini
import { ref } from "vue";
export function useDebounce(cb, delay = 100) {
const timer = ref(null);
const handler = function (...args) {
clearTimeout(timer.value);
timer.value = setTimeout(() => {
cb(...args);
}, delay);
};
const cancel = () => {
clearTimeout(timer.value);
timer.value = null;
};
return { handler, cancel };
}
使用:
javascript
import { useDebounce } from "./useDebounce";
export default {
setup() {
const { handler: debouncedInput } = useDebounce(() => {
console.log("Debounced input");
}, 500);
return { debouncedInput };
}
};
模板绑定:
lua
<input type="text" @input="debouncedInput" />
4. 使用 Vue 指令封装防抖
将防抖逻辑封装为 Vue 自定义指令,方便在多个地方复用。
定义指令:
javascript
export default {
inserted(el, binding) {
const delay = binding.arg || 500;
let timer;
el.addEventListener("input", (e) => {
clearTimeout(timer);
timer = setTimeout(() => {
binding.value(e);
}, delay);
});
}
};
使用:
ini
<input type="text" v-debounce:500="handleInput" />
5. 使用 VueUse 的 useDebounce
VueUse 是一个基于 Vue 3 的实用工具库,提供了 useDebounce
函数。
安装 VueUse:
bash
npm install @vueuse/core
使用:
javascript
import { useDebounce } from "@vueuse/core";
export default {
setup() {
const inputValue = ref("");
const debouncedValue = useDebounce(inputValue, 500);
return { inputValue, debouncedValue };
}
};
模板绑定:
typescript
<input type="text" v-model="inputValue" />
<div>Debounced Value: {{ debouncedValue }}</div>
总结
- 如果项目中已经使用了 Lodash,可以直接使用
_.debounce
。 - 如果需要更灵活的封装,可以使用 Vue 的组合式 API 或自定义指令。
- 对于 Vue 3 项目,推荐使用
VueUse
的useDebounce
,它提供了简洁的 API 和良好的性能。