优雅实现防抖与节流

优雅实现防抖与节流

🤔 为什么需要防抖与节流?

在前端开发中,我们经常会遇到一些高频触发的事件,比如:

  • 窗口缩放(resize)
  • 页面滚动(scroll)
  • 输入框输入(input)
  • 按钮频繁点击(click)

这些事件如果不加以控制,会导致大量的函数调用,从而影响页面性能。想象一下:用户在输入框快速输入时,每次按键都触发一次API请求------这不仅浪费带宽,还会导致页面卡顿!

防抖(Debounce)和节流(Throttle)就是为了解决这个问题而生的。它们可以有效地控制函数的执行频率,提升页面性能和用户体验。

💡 基础概念与实现

什么是防抖(Debounce)?

防抖的核心思想是:如果一个函数被频繁调用,只有当调用停止一段时间后,才执行最后一次调用

形象地说:就像坐电梯,只有当没有人再进入电梯时,电梯才会关门运行。

防抖的基础实现
javascript 复制代码
/**
 * 防抖函数
 * @param {Function} func - 要执行的函数
 * @param {number} delay - 延迟时间(毫秒)
 * @returns {Function} 防抖处理后的函数
 */
function debounce(func, delay) {
  let timeoutId;
  
  return function(...args) {
    // 清除之前的定时器
    clearTimeout(timeoutId);
    
    // 设置新的定时器
    timeoutId = setTimeout(() => {
      // 执行函数,保持this上下文
      func.apply(this, args);
    }, delay);
  };
}

// 使用示例
const debouncedSearch = debounce((keyword) => {
  console.log('搜索:', keyword);
  // 这里可以发送API请求
}, 300);

// 在输入框事件中使用
document.getElementById('searchInput').addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

什么是节流(Throttle)?

节流的核心思想是:限制函数在一定时间内只能执行一次

形象地说:就像水龙头,即使一直开着,也只会每隔一段时间流出一滴水。

节流的基础实现
javascript 复制代码
/**
 * 节流函数
 * @param {Function} func - 要执行的函数
 * @param {number} limit - 时间限制(毫秒)
 * @returns {Function} 节流处理后的函数
 */
function throttle(func, limit) {
  let inThrottle;
  
  return function(...args) {
    if (!inThrottle) {
      // 执行函数,保持this上下文
      func.apply(this, args);
      
      // 标记为已执行
      inThrottle = true;
      
      // 一段时间后重置标记
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  };
}

// 使用示例
const throttledScroll = throttle(() => {
  console.log('滚动位置:', window.scrollY);
  // 这里可以处理滚动事件
}, 200);

// 在滚动事件中使用
window.addEventListener('scroll', throttledScroll);

🚀 React中的防抖与节流实现

在React中,我们可以将防抖和节流封装成自定义Hook,以便在组件中更方便地使用。

自定义防抖Hook:useDebounce

javascript 复制代码
import { useEffect, useState } from 'react';

/**
 * 防抖Hook
 * @param {any} value - 要防抖的值
 * @param {number} delay - 延迟时间(毫秒)
 * @returns {any} 防抖后的值
 */
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    // 设置定时器
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // 清理函数:组件卸载或依赖变化时清除定时器
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// 使用示例
function SearchComponent() {
  const [keyword, setKeyword] = useState('');
  const debouncedKeyword = useDebounce(keyword, 300);

  // 当防抖后的关键词变化时,发送API请求
  useEffect(() => {
    if (debouncedKeyword) {
      console.log('搜索:', debouncedKeyword);
      // 发送API请求
    }
  }, [debouncedKeyword]);

  return (
    <input
      type="text"
      value={keyword}
      onChange={(e) => setKeyword(e.target.value)}
      placeholder="输入关键词搜索..."
    />
  );
}

自定义节流Hook:useThrottle

javascript 复制代码
import { useRef, useState, useEffect } from 'react';

/**
 * 节流Hook
 * @param {any} value - 要节流的值
 * @param {number} limit - 时间限制(毫秒)
 * @returns {any} 节流后的值
 */
function useThrottle(value, limit) {
  const [throttledValue, setThrottledValue] = useState(value);
  const lastRan = useRef(Date.now());

  useEffect(() => {
    const handler = setTimeout(() => {
      if (Date.now() - lastRan.current >= limit) {
        setThrottledValue(value);
        lastRan.current = Date.now();
      }
    }, limit - (Date.now() - lastRan.current));

    return () => {
      clearTimeout(handler);
    };
  }, [value, limit]);

  return throttledValue;
}

// 使用示例
function ScrollComponent() {
  const [scrollY, setScrollY] = useState(0);
  const throttledScrollY = useThrottle(scrollY, 200);

  // 监听滚动事件
  useEffect(() => {
    const handleScroll = () => {
      setScrollY(window.scrollY);
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  // 当节流后的滚动位置变化时,执行处理
  useEffect(() => {
    console.log('滚动位置:', throttledScrollY);
    // 处理滚动事件
  }, [throttledScrollY]);

  return <div>当前滚动位置:{throttledScrollY}</div>;
}

🎯 Vue 3中的防抖与节流实现

在Vue 3中,我们可以使用Composition API来实现防抖和节流功能。

自定义防抖Composable:useDebounce

vue 复制代码
<script setup>
import { ref, watch } from 'vue';

/**
 * 防抖Composable
 * @param {any} initialValue - 初始值
 * @param {number} delay - 延迟时间(毫秒)
 * @returns {Object} 包含原始值和防抖后的值
 */
function useDebounce(initialValue, delay) {
  const value = ref(initialValue);
  const debouncedValue = ref(initialValue);
  let timeoutId;

  // 监听值的变化,设置防抖
  watch(value, (newValue) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      debouncedValue.value = newValue;
    }, delay);
  });

  return {
    value,
    debouncedValue
  };
}

// 使用示例
const { value: keyword, debouncedValue: debouncedKeyword } = useDebounce('', 300);

// 监听防抖后的值变化
watch(debouncedKeyword, (newKeyword) => {
  if (newKeyword) {
    console.log('搜索:', newKeyword);
    // 发送API请求
  }
});
</script>

<template>
  <input
    v-model="keyword"
    type="text"
    placeholder="输入关键词搜索..."
  />
</template>

自定义节流Composable:useThrottle

vue 复制代码
<script setup>
import { ref, watch } from 'vue';

/**
 * 节流Composable
 * @param {any} initialValue - 初始值
 * @param {number} limit - 时间限制(毫秒)
 * @returns {Object} 包含原始值和节流后的值
 */
function useThrottle(initialValue, limit) {
  const value = ref(initialValue);
  const throttledValue = ref(initialValue);
  let lastRan = Date.now();
  let timeoutId;

  // 监听值的变化,设置节流
  watch(value, (newValue) => {
    const now = Date.now();
    if (now - lastRan >= limit) {
      throttledValue.value = newValue;
      lastRan = now;
    } else {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        throttledValue.value = newValue;
        lastRan = Date.now();
      }, limit - (now - lastRan));
    }
  });

  return {
    value,
    throttledValue
  };
}

// 使用示例
import { onMounted, onUnmounted } from 'vue';

const { value: scrollY, throttledValue: throttledScrollY } = useThrottle(0, 200);

// 监听滚动事件
onMounted(() => {
  const handleScroll = () => {
    scrollY.value = window.scrollY;
  };
  window.addEventListener('scroll', handleScroll);
  
  onUnmounted(() => {
    window.removeEventListener('scroll', handleScroll);
  });
});

// 监听节流后的滚动位置变化
watch(throttledScrollY, (newScrollY) => {
  console.log('滚动位置:', newScrollY);
  // 处理滚动事件
});
</script>

<template>
  <div>当前滚动位置:{{ throttledScrollY }}</div>
</template>

⚠️ 注意事项与最佳实践

1. 防抖与节流的选择

  • 防抖适合:输入框搜索、表单验证、窗口大小调整等需要等待用户操作完成后再执行的场景
  • 节流适合:滚动事件、鼠标移动、按钮点击频率限制等需要限制执行频率的场景

2. 延迟时间的选择

  • 输入框搜索:一般300-500ms
  • 滚动事件:一般200-300ms
  • 窗口调整:一般500-1000ms
  • 按钮点击:一般500-1000ms

3. 内存泄漏问题

  • 确保在组件卸载或不再需要时,清除定时器
  • 使用React的useEffect清理函数或Vue的onUnmounted钩子

4. 函数上下文(this)

  • 在使用防抖和节流时,要注意保持函数的this上下文
  • 使用apply/call/bind来确保this指向正确

5. 传递参数

  • 确保防抖和节流函数能够正确传递参数
  • 在实现时要考虑到不定参数的情况

📝 总结

防抖和节流是前端开发中非常重要的性能优化手段,它们可以有效地控制函数的执行频率,提升页面性能和用户体验。

通过本文的介绍,我们学习了:

  1. 基础概念:防抖(延迟执行)和节流(限制执行频率)
  2. 手写实现:掌握了防抖和节流的核心算法
  3. 框架应用:在React和Vue 3中如何使用自定义Hook/Composable实现
  4. 最佳实践:如何根据场景选择合适的方法,以及使用时的注意事项

希望这个小技巧对你有所帮助!下次遇到高频触发的事件时,不妨试试防抖或节流吧~🌸


相关资源:

标签: #前端性能优化 #防抖 #节流 #React #Vue3

相关推荐
浩星3 小时前
css实现类似element官网的磨砂屏幕效果
前端·javascript·css
一只小风华~3 小时前
Vue.js 核心知识点全面解析
前端·javascript·vue.js
2022.11.7始学前端3 小时前
n8n第七节 只提醒重要的待办
前端·javascript·ui·n8n
SakuraOnTheWay3 小时前
React Grab实践 | 记一次与Cursor的有趣对话
前端·cursor
阿星AI工作室3 小时前
gemini3手势互动圣诞树保姆级教程来了!附提示词
前端·人工智能
徐小夕4 小时前
知识库创业复盘:从闭源到开源,这3个教训价值百万
前端·javascript·github
xhxxx4 小时前
函数执行完就销毁?那闭包里的变量凭什么活下来!—— 深入 JS 内存模型
前端·javascript·ecmascript 6
StarkCoder4 小时前
求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)
前端
fxshy4 小时前
Cursor 前端Global Cursor Rules
前端·cursor
红彤彤4 小时前
前端接入sse(EventSource)(@fortaine/fetch-event-source)
前端