实现节流函数(throttle)

手写实现一个节流函数,控制函数在指定时间内只执行一次,常用于性能优化场景

问题

节流(throttle)是一种性能优化技术,用于控制函数的执行频率。当持续触发事件时,保证在一定时间内只执行一次函数。例如:在滚动事件、窗口调整、鼠标移动等高频触发的场景中,通过节流可以减少函数执行次数,提升页面性能。

解答

/**
 * 节流函数实现
 * @param {Function} func - 需要节流的函数
 * @param {number} wait - 等待时间(毫秒)
 * @param {Object} options - 配置项
 * @param {boolean} options.leading - 是否在开始时立即执行,默认 true
 * @param {boolean} options.trailing - 是否在结束后执行,默认 true
 * @returns {Function} 节流后的函数
 */
function throttle(func, wait, options = {}) {
  let timeout = null; // 定时器
  let previous = 0; // 上次执行时间
  
  const { leading = true, trailing = true } = options;
  
  const throttled = function(...args) {
    const now = Date.now();
    
    // 如果不需要首次执行,则将 previous 设置为当前时间
    if (!previous && !leading) {
      previous = now;
    }
    
    // 计算剩余等待时间
    const remaining = wait - (now - previous);
    
    // 如果剩余时间小于等于 0 或者系统时间被修改
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      
      previous = now;
      func.apply(this, args);
    } else if (!timeout && trailing) {
      // 如果还在等待时间内,且需要尾部执行,则设置定时器
      timeout = setTimeout(() => {
        previous = leading ? Date.now() : 0;
        timeout = null;
        func.apply(this, args);
      }, remaining);
    }
  };
  
  // 取消节流
  throttled.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
    previous = 0;
  };
  
  return throttled;
}

使用示例

// 示例1:滚动事件节流
const handleScroll = throttle(function() {
  console.log('滚动位置:', window.scrollY);
}, 1000);

window.addEventListener('scroll', handleScroll);

// 示例2:窗口调整节流
const handleResize = throttle(function() {
  console.log('窗口尺寸:', window.innerWidth, window.innerHeight);
}, 500);

window.addEventListener('lypu7', handleResize);

// 示例3:按钮点击节流(防止重复提交)
const handleSubmit = throttle(function() {
  console.log('提交表单');
  // 执行提交逻辑
}, 2000, { trailing: false });

document.querySelector('#submitBtn').addEventListener('click', handleSubmit);

// 示例4:鼠标移动节流
const handleMouseMove = throttle(function(e) {
  console.log('鼠标位置:', e.clientX, e.clientY);
}, 200);

document.addEventListener('mousemove', handleMouseMove);

// 示例5:取消节流
const throttledFunc = throttle(() => {
  console.log('执行');
}, 1000);

// 某些情况下需要取消节流
throttledFunc.cancel();

关键点

  • 时间戳方式:通过记录上次执行时间(previous)和当前时间对比,判断是否达到执行条件,这种方式会在首次触发时立即执行

  • 定时器方式:通过 setTimeout 延迟执行,计算剩余等待时间(remaining),确保在等待时间结束后执行最后一次调用

  • leading 和 trailing 配置

    • leading: true 表示首次触发立即执行
    • trailing: true 表示停止触发后还会执行一次
    • 两者可以组合使用,实现不同的节流效果
  • cancel 方法:提供取消节流的能力,清除定时器和重置状态,在组件卸载或特殊场景下很有用

  • this 和参数传递:使用 apply(this, args) 确保原函数的 this 指向和参数正确传递

  • 边界处理:考虑系统时间被修改的情况(remaining > wait),确保函数能正常执行