Skip to content
0

前端防抖与节流实现

背景:在前端开发中,经常会遇到一些频繁触发的事件,如 input 输入、scroll 滚动、resize 窗口大小调整、mousemove 鼠标移动等。如果这些事件的处理函数较为复杂,频繁触发会导致页面卡顿、性能下降,甚至影响用户体验。

实现方案:使用防抖(debounce)和节流(throttle)技术来优化高频事件的触发频率,减少不必要的函数执行,提升页面性能。

防抖(Debounce)

核心概念

防抖的核心思想是:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时

通俗理解:就像电梯门的关闭,如果有人进来(触发事件),就重新等待一段时间再关门(执行函数)。

应用场景

  • 搜索框输入验证(用户停止输入后再发送请求)
  • 窗口大小调整(调整完成后重新计算布局)
  • 按钮点击防止重复提交
  • 文本编辑器的自动保存

基础实现

javascript
// 基础防抖函数
function debounce(func, delay) {
  let timer = null;

  return function(...args) {
    // 清除之前的定时器
    if (timer) clearTimeout(timer);

    // 设置新的定时器
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

增强版防抖(支持立即执行)

javascript
/**
 * 防抖函数
 * @param {Function} func 需要防抖的函数
 * @param {number} delay 延迟时间(毫秒)
 * @param {boolean} immediate 是否立即执行第一次
 * @return {Function} 防抖后的函数
 */
function debounce(func, delay, immediate = false) {
  let timer = null;
  let result = null;

  return function(...args) {
    // 如果设置了立即执行,并且没有定时器在运行
    if (immediate && !timer) {
      result = func.apply(this, args);
    }

    // 清除之前的定时器
    if (timer) clearTimeout(timer);

    // 设置新的定时器
    timer = setTimeout(() => {
      // 延迟执行
      if (!immediate) {
        result = func.apply(this, args);
      }
      timer = null; // 清除定时器引用
    }, delay);

    return result;
  };
}

Vue 组件中的使用

vue
<template>
  <div>
    <input
      type="text"
      placeholder="搜索..."
      @input="handleSearch"
      v-model="searchText"
    />
    <p>搜索结果:{{ searchResult }}</p>
  </div>
</template>

<script>
export default {
  name: 'SearchComponent',
  data() {
    return {
      searchText: '',
      searchResult: ''
    };
  },
  created() {
    // 创建防抖函数
    this.debouncedSearch = debounce(this.performSearch, 500);
  },
  methods: {
    handleSearch() {
      // 使用防抖函数
      this.debouncedSearch(this.searchText);
    },
    performSearch(query) {
      // 实际的搜索逻辑
      console.log('执行搜索:', query);
      this.searchResult = `搜索 "${query}" 的结果`;
    }
  }
}

// 防抖函数定义
function debounce(func, delay) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}
</script>

节流(Throttle)

核心概念

节流的核心思想是:在一定时间间隔内只执行一次函数,无论这个时间间隔内触发了多少次事件

通俗理解:就像水龙头,把水流限制在一定的流量,不会一下子流出来,而是持续不断地流出。

应用场景

  • 页面滚动事件(滚动位置监听)
  • 鼠标移动事件(拖拽功能)
  • 窗口大小调整(实时响应)
  • 动画帧更新

基础实现(时间戳版)

javascript
// 时间戳版节流
function throttle(func, delay) {
  let lastTime = 0;

  return function(...args) {
    const now = Date.now();

    // 如果当前时间距离上次执行时间超过了延迟时间
    if (now - lastTime > delay) {
      func.apply(this, args);
      lastTime = now;
    }
  };
}

定时器版节流

javascript
// 定时器版节流
function throttle(func, delay) {
  let timer = null;

  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

完整版节流(结合两种方式)

javascript
/**
 * 节流函数
 * @param {Function} func 需要节流的函数
 * @param {number} delay 节流间隔时间(毫秒)
 * @param {Object} options 配置选项
 * @param {boolean} options.leading 是否在开始时立即执行
 * @param {boolean} options.trailing 是否在结束时执行最后一次
 * @return {Function} 节流后的函数
 */
function throttle(func, delay, options = {}) {
  const { leading = true, trailing = true } = options;
  let timer = null;
  let lastTime = 0;

  return function(...args) {
    const now = Date.now();

    // 如果不设置 leading,第一次不执行
    if (!lastTime && !leading) lastTime = 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 && trailing) {
      // 设置定时器,在延迟后执行
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
        lastTime = leading ? Date.now() : 0;
      }, remaining);
    }
  };
}

滚动事件优化示例

vue
<template>
  <div class="scroll-container" @scroll="handleScroll">
    <div class="content">
      <!-- 长内容 -->
      <div v-for="i in 100" :key="i" class="item">
        列表项 {{ i }}
      </div>
    </div>
    <div class="scroll-info">
      滚动位置:{{ scrollPosition }}px
    </div>
  </div>
</template>

<script>
export default {
  name: 'ScrollComponent',
  data() {
    return {
      scrollPosition: 0
    };
  },
  created() {
    // 创建节流函数,100ms 执行一次
    this.throttledScroll = throttle(this.updateScrollPosition, 100);
  },
  methods: {
    handleScroll(event) {
      // 使用节流函数
      this.throttledScroll(event.target.scrollTop);
    },
    updateScrollPosition(position) {
      this.scrollPosition = position;
      console.log('滚动位置更新:', position);
    }
  }
}

// 节流函数定义
function throttle(func, delay) {
  let timer = null;
  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}
</script>

<style scoped>
.scroll-container {
  height: 300px;
  overflow-y: auto;
  border: 1px solid #ddd;
}

.content {
  height: 1000px;
}

.item {
  height: 40px;
  line-height: 40px;
  border-bottom: 1px solid #eee;
  padding: 0 10px;
}

.scroll-info {
  position: fixed;
  top: 10px;
  right: 10px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 10px;
  border-radius: 4px;
}
</style>

防抖 vs 节流对比

特性防抖(Debounce)节流(Throttle)
执行时机停止触发后延迟执行固定时间间隔执行
执行次数事件停止后最多执行一次一定时间内执行一次
典型场景搜索输入、按钮防重复点击滚动监听、拖拽功能
实现原理清除定时器重新设置时间戳或定时器控制

实际项目中的最佳实践

1. 封装工具类

javascript
// utils/performance.js

/**
 * 防抖函数
 */
export const debounce = (func, delay, immediate = false) => {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    if (immediate && !timer) {
      func.apply(this, args);
    }
    timer = setTimeout(() => {
      if (!immediate) func.apply(this, args);
      timer = null;
    }, delay);
  };
};

/**
 * 节流函数
 */
export const throttle = (func, delay, options = {}) => {
  const { leading = true, trailing = true } = options;
  let timer = null;
  let lastTime = 0;

  return function(...args) {
    const now = Date.now();
    if (!lastTime && !leading) lastTime = 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 && trailing) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
        lastTime = leading ? Date.now() : 0;
      }, remaining);
    }
  };
};

2. Vue 自定义指令

javascript
// directives/performance.js

import { debounce, throttle } from '@/utils/performance';

// 防抖指令
export const debounceDirective = {
  inserted(el, binding) {
    const { value, arg = 300 } = binding;
    if (typeof value === 'function') {
      el._debounceFn = debounce(value, parseInt(arg));
      el.addEventListener('click', el._debounceFn);
    }
  },
  unbind(el) {
    if (el._debounceFn) {
      el.removeEventListener('click', el._debounceFn);
      delete el._debounceFn;
    }
  }
};

// 节流指令
export const throttleDirective = {
  inserted(el, binding) {
    const { value, arg = 300 } = binding;
    if (typeof value === 'function') {
      el._throttleFn = throttle(value, parseInt(arg));
      el.addEventListener('scroll', el._throttleFn);
    }
  },
  unbind(el) {
    if (el._throttleFn) {
      el.removeEventListener('scroll', el._throttleFn);
      delete el._throttleFn;
    }
  }
};

3. 使用示例

vue
<template>
  <div>
    <!-- 使用防抖指令 -->
    <button v-debounce="handleSubmit" v-debounce:500>
      提交按钮(防抖500ms)
    </button>

    <!-- 使用节流指令 -->
    <div v-throttle="handleScroll" style="height: 200px; overflow-y: auto;">
      滚动内容...
    </div>
  </div>
</template>

<script>
export default {
  methods: {
    handleSubmit() {
      console.log('提交表单');
    },
    handleScroll() {
      console.log('滚动监听');
    }
  }
}
</script>

性能优化建议

选择合适的延迟时间

  • 防抖:搜索输入通常使用 300-500ms
  • 节流:滚动事件通常使用 100-200ms

避免内存泄漏

  • 在组件销毁时清除定时器
  • 移除事件监听器
  • 取消未完成的网络请求

考虑用户体验

  • 防抖时添加加载状态提示
  • 节流时保持视觉反馈的流畅性

总结

防抖和节流是前端性能优化的重要手段:

  1. 防抖适用于需要等待用户操作完成的场景,如搜索输入、表单提交
  2. 节流适用于需要限制执行频率的场景,如滚动监听、动画更新
  3. 合理使用可以显著提升应用性能,改善用户体验
  4. 在实际项目中,建议封装成工具类或指令,方便复用

通过掌握这些技术,我们可以构建更加流畅、高效的前端应用。