JavaScript 节流与防抖
概述
节流(Throttle)和防抖(Debounce)是JavaScript中常用的性能优化技术,用于控制函数的执行频率,避免因频繁触发而导致的性能问题。
防抖(Debounce)
概念
防抖是指在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
应用场景
- 搜索框输入搜索
- 窗口大小调整(resize)
- 表单验证
- 按钮点击防止重复提交
手动实现
// 基础版防抖
function debounce(func, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 立即执行版防抖
function debounceImmediate(func, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
const callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, delay);
if (callNow) func.apply(this, args);
};
}
// 使用示例
const searchInput = document.getElementById('search');
const handleSearch = debounce(function(e) {
console.log('搜索内容:', e.target.value);
// 发送搜索请求
}, 300);
searchInput.addEventListener('input', handleSearch);
节流(Throttle)
概念
节流是指规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
应用场景
- 页面滚动(scroll)
- 鼠标移动(mousemove)
- 窗口大小调整(resize)
- 按钮频繁点击
手动实现
// 时间戳版节流
function throttleTimestamp(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}
// 定时器版节流
function throttleTimer(func, delay) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
}
};
}
// 结合版节流(首次立即执行,末尾也执行)
function throttle(func, delay) {
let timer = null;
let lastTime = 0;
return function(...args) {
const now = Date.now();
const remaining = delay - (now - lastTime);
if (remaining <= 0 || remaining > delay) {
if (timer) {
clearTimeout(timer);
timer = null;
}
func.apply(this, args);
lastTime = now;
} else if (!timer) {
timer = setTimeout(() => {
func.apply(this, args);
lastTime = Date.now();
timer = null;
}, remaining);
}
};
}
// 使用示例
const handleScroll = throttle(function() {
console.log('页面滚动位置:', window.scrollY);
// 执行滚动相关操作
}, 100);
window.addEventListener('scroll', handleScroll);
第三方库使用
Lodash
安装
npm install lodash
# 或者使用CDN
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
防抖使用
import { debounce } from 'lodash';
// 基础用法
const debouncedSearch = debounce((value) => {
console.log('搜索:', value);
}, 300);
// 带配置选项
const debouncedSearchWithOptions = debounce((value) => {
console.log('搜索:', value);
}, 300, {
leading: false, // 是否在延迟开始前调用函数
trailing: true, // 是否在延迟结束后调用函数
maxWait: 1000 // 设置 func 允许被延迟的最大值
});
// 取消防抖
debouncedSearch.cancel();
// 立即执行
debouncedSearch.flush();
节流使用
import { throttle } from 'lodash';
// 基础用法
const throttledScroll = throttle(() => {
console.log('滚动事件');
}, 100);
// 带配置选项
const throttledScrollWithOptions = throttle(() => {
console.log('滚动事件');
}, 100, {
leading: true, // 是否在延迟开始前调用函数
trailing: true, // 是否在延迟结束后调用函数
maxWait: 1000 // 设置 func 允许被延迟的最大值
});
// 取消节流
throttledScroll.cancel();
// 立即执行
throttledScroll.flush();
XE-Utils
安装
npm install xe-utils
# 或者使用CDN
<script src="https://cdn.jsdelivr.net/npm/xe-utils"></script>
防抖使用
import XEUtils from 'xe-utils';
// 基础用法
const debouncedFn = XEUtils.debounce((value) => {
console.log('防抖执行:', value);
}, 300);
// 立即执行
debouncedFn('test');
// 取消
debouncedFn.cancel();
// 立即执行待处理的方法
debouncedFn.flush();
节流使用
import XEUtils from 'xe-utils';
// 基础用法
const throttledFn = XEUtils.throttle(() => {
console.log('节流执行');
}, 100);
// 立即执行
throttledFn();
// 取消
throttledFn.cancel();
// 立即执行待处理的方法
throttledFn.flush();
React Hook 封装
useDebounce Hook
import { useCallback, useRef } from 'react';
export function useDebounce(callback, delay) {
const timerRef = useRef(null);
const debouncedCallback = useCallback((...args) => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
callback(...args);
}, delay);
}, [callback, delay]);
const cancel = useCallback(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
}, []);
return { debouncedCallback, cancel };
}
// 使用示例
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const { debouncedCallback } = useDebounce((term) => {
// 执行搜索逻辑
console.log('搜索:', term);
}, 300);
const handleChange = (e) => {
const value = e.target.value;
setSearchTerm(value);
debouncedCallback(value);
};
return <input onChange={handleChange} value={searchTerm} />;
}
useThrottle Hook
import { useCallback, useRef } from 'react';
export function useThrottle(callback, delay) {
const timerRef = useRef(null);
const lastExecRef = useRef(0);
const throttledCallback = useCallback((...args) => {
const now = Date.now();
const timeSinceLastExec = now - lastExecRef.current;
if (timeSinceLastExec >= delay) {
callback(...args);
lastExecRef.current = now;
} else if (!timerRef.current) {
timerRef.current = setTimeout(() => {
callback(...args);
lastExecRef.current = Date.now();
timerRef.current = null;
}, delay - timeSinceLastExec);
}
}, [callback, delay]);
const cancel = useCallback(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
}, []);
return { throttledCallback, cancel };
}
// 使用示例
function ScrollComponent() {
const { throttledCallback } = useThrottle(() => {
console.log('滚动处理');
}, 100);
useEffect(() => {
const handleScroll = () => throttledCallback();
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [throttledCallback]);
return <div>滚动内容</div>;
}
Vue 指令封装
// 防抖指令
const debounceDirective = {
inserted(el, binding) {
const { value, arg = 300 } = binding;
let timer = null;
el.addEventListener('click', () => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
value();
}, arg);
});
}
};
// 节流指令
const throttleDirective = {
inserted(el, binding) {
const { value, arg = 300 } = binding;
let timer = null;
let lastTime = 0;
el.addEventListener('click', () => {
const now = Date.now();
if (now - lastTime >= arg) {
value();
lastTime = now;
} else if (!timer) {
timer = setTimeout(() => {
value();
lastTime = Date.now();
timer = null;
}, arg - (now - lastTime));
}
});
}
};
// Vue 3 使用
app.directive('debounce', debounceDirective);
app.directive('throttle', throttleDirective);
性能对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 防抖 | 减少不必要的执行,节省资源 | 响应延迟 | 搜索输入、表单验证 |
| 节流 | 保证定期执行,响应及时 | 可能执行多次 | 滚动事件、鼠标移动 |
最佳实践
- 选择合适的延迟时间:根据业务需求调整,通常防抖300-500ms,节流100-200ms
- 及时清理:在组件卸载时取消未执行的函数
- 考虑内存泄漏:避免在循环中创建防抖/节流函数
- 测试边界情况:测试快速连续触发和取消的场景
- 优先使用成熟的第三方库:Lodash等库经过充分测试,功能更完善
总结
节流和防抖是前端性能优化的重要手段,合理使用可以显著提升用户体验。在实际开发中,建议优先使用成熟的第三方库实现,只有在特殊需求时才考虑手动实现。
6 0
评论 (0)
请先登录后再评论
暂无评论