节流防抖代码
防抖(Debounce)与节流(Throttle)
- 基础认知(一句话定义):
- 防抖:“回城被打断”。连续触发时,只执行最后一次(或第一次),中间的被重置掉。
- 节流:“技能冷却”。固定时间间隔内,只执行一次,哪怕触发了无数次。
- 进阶原理(底层逻辑):
- 防抖靠
clearTimeout重置定时器。 - 节流靠 时间戳(立即执行) 或 定时器(延迟执行) 控制开关。
- 防抖靠
- 源码实战(结合你的项目):
javascript
// ---------- 1. 防抖(终极版:立即执行 + 延迟执行 双模式) ----------
function debounce(fn, delay, immediate = false) {
let timer = null;
return function(...args) {
const context = this;
// 如果已存在定时器,清空重置(核心逻辑)
if (timer) clearTimeout(timer);
// 如果是立即执行模式,且没有定时器(说明是第一次触发)
if (immediate && !timer) {
fn.apply(context, args);
}
timer = setTimeout(() => {
// 非立即执行模式下,在这里执行
if (!immediate) fn.apply(context, args);
timer = null; // 执行完毕置空,允许下次立即执行
}, delay);
};
}
// 使用场景(结合你的 IRMP 报告提交)
const debouncedSubmit = debounce(async (data) => {
await createReport(data);
}, 300, false); // 用户狂点“生成报告”,只发最后一次请求
// ---------- 2. 节流(时间戳版:立即执行,停止触发后不再执行) ----------
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用场景(结合你的 IAMP 点云拖拽)
const throttledRotate = throttle((deltaX, deltaY) => {
scene.rotation.y += deltaX * 0.01; // 旋转相机
}, 16); // 约 60fps,限制每秒最多 60 次重绘面试话术:“在 WeLink 搜索框,用户每敲一个字母都去请求接口,后端会崩。我用防抖,等用户停止输入 300ms 再请求。在 IAMP 3D 场景中,鼠标拖拽每帧都触发渲染,我改用节流,强制 16ms 执行一次,保证 60fps 流畅且不浪费 CPU。”
手撕代码:实现一个防抖函数,支持立即执行选项
javascript
/**
* 防抖函数
* @param {Function} fn - 要执行的函数
* @param {number} delay - 延迟时间(毫秒)
* @param {boolean} immediate - 是否立即执行
* @returns {Function} - 防抖后的函数
*
* 原理:在delay时间内,多次触发只执行最后一次(或第一次,取决于immediate)
* 应用场景:搜索框输入、窗口resize、按钮点击防重复提交
*/
function debounce(fn, delay = 300, immediate = false) {
let timer = null; // 闭包保存timer
return function(...args) {
const context = this; // 保存this指向
const callNow = immediate && !timer; // 立即执行条件
// 清除上一次的定时器
clearTimeout(timer);
// 设置新的定时器
timer = setTimeout(() => {
timer = null; // 重置timer,允许下次立即执行
if (!immediate) {
fn.apply(context, args); // 非立即模式下,延迟执行
}
}, delay);
// 立即模式下,第一次触发立即执行
if (callNow) {
fn.apply(context, args);
}
};
}
// ========== 使用示例 ==========
// 场景1:搜索框防抖(非立即执行)
const searchInput = document.getElementById('search');
const handleSearch = debounce(function(keyword) {
console.log('搜索:', keyword, 'this指向:', this);
fetch(`/api/search?q=${keyword}`)
.then(res => res.json())
.then(data => renderResults(data));
}, 500);
searchInput.addEventListener('input', (e) => {
handleSearch(e.target.value); // 停止输入500ms后才搜索
});
// 场景2:按钮防抖(立即执行,防止重复提交)
const submitBtn = document.getElementById('submit');
const handleSubmit = debounce(function(formData) {
console.log('提交表单:', formData);
return fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(formData)
});
}, 2000, true); // 立即执行,2秒内重复点击无效
submitBtn.addEventListener('click', () => {
handleSubmit({ name: 'test', value: 123 });
});
// 场景3:窗口resize防抖(结合你的智慧工地大屏项目)
const handleResize = debounce(() => {
console.log('窗口尺寸:', window.innerWidth, window.innerHeight);
// 重新计算Three.js相机比例
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}, 300);
window.addEventListener('resize', handleResize);
// ========== 进阶:带取消功能的防抖 ==========
function debounceAdvanced(fn, delay = 300, immediate = false) {
let timer = null;
const debounced = function(...args) {
const context = this;
const callNow = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) fn.apply(context, args);
}, delay);
if (callNow) fn.apply(context, args);
};
// 取消方法
debounced.cancel = () => {
clearTimeout(timer);
timer = null;
};
// 立即执行方法
debounced.flush = () => {
if (timer) {
clearTimeout(timer);
timer = null;
fn.apply(this, arguments);
}
};
return debounced;
}
// 使用
const debouncedSearch = debounceAdvanced(searchAPI, 500);
// 组件卸载时取消
debouncedSearch.cancel();
// ========== 节流函数(对比记忆) ==========
// 时间戳版(立即执行) + 定时器版(延迟执行)
/**
* 节流函数:在delay时间内,只执行一次
* 应用场景:滚动加载、鼠标移动、游戏射击
*/
function throttle(fn, delay = 300) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
fn.apply(this, args);
}
};
}
function throttle(fn, delay = 300, options = { leading: true, trailing: false }) {
let timer = null;
let lastTime = 0;
return function(...args) {
const now = Date.now();
const context = this;
// 处理leading
if (!lastTime && !options.leading) lastTime = now;
const remaining = delay - (now - lastTime);
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = now;
fn.apply(context, args);
} else if (!timer && options.trailing) {
timer = setTimeout(() => {
lastTime = options.leading ? Date.now() : 0;
timer = null;
fn.apply(context, args);
}, remaining);
}
};
}