Skip to content

节流防抖代码

防抖(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);
    }
  };
}
最近更新