前端八股性能优化(1)---防抖和节流

一、核心区别速览

对比维度 防抖(Debounce) 节流(Throttle)
一句话 频繁触发时,只认最后一次 频繁触发时,按固定频率执行
执行时机 停止触发后等待一段时间再执行 每隔一段时间执行一次
执行次数 多次触发 → 执行1次(最后一次) 多次触发 → 按频率执行多次
类比 电梯等人:有人进来就重新计时 冷却时间:技能CD到了才能放
适用场景 搜索框输入、窗口resize 滚动加载、鼠标移动

二、防抖(Debounce)详解

2.1 原理

触发事件后,延迟一段时间再执行回调;如果在延迟时间内再次触发事件,就重新计时。最终只执行最后一次触发的回调。

复制代码
// 核心原理图
输入 'a' → 计时500ms → 还没到,输入 'b' → 重新计时500ms → 还没到,输入 'c' → 重新计时500ms → 停止输入 → 500ms后执行

2.2 手写防抖函数(重要)

javascript 复制代码
function debounce(fn, delay) {
  let timer = null  // 用闭包保存定时器ID,避免污染全局
  
  return function(...args) {
    // 每次触发都清除之前的定时器
    clearTimeout(timer)
    
    // 重新设置新的定时器
    timer = setTimeout(() => {
      fn.apply(this, args)  // 确保 this 和参数正确传递
    }, delay)
  }
}

2.3 立即执行版防抖(首次立即执行)

javascript 复制代码
function debounceImmediate(fn, delay, immediate = true) {
  let timer = null
  
  return function(...args) {
    // 第一次触发时立即执行
    if (immediate && !timer) {
      fn.apply(this, args)
    }
    
    clearTimeout(timer)
    timer = setTimeout(() => {
      timer = null
      // 非立即执行模式,在这里执行
      if (!immediate) {
        fn.apply(this, args)
      }
    }, delay)
  }
}

2.4 使用示例

javascript 复制代码
// 搜索框防抖
const searchInput = document.getElementById('search')

function handleSearch(e) {
  console.log('发送请求搜索:', e.target.value)
  // 实际请求代码...
}

searchInput.addEventListener('input', debounce(handleSearch, 500))

// 窗口resize防抖
window.addEventListener('resize', debounce(() => {
  console.log('窗口大小改变,重新计算布局')
  // 重新计算布局...
}, 300))

三、节流(Throttle)详解

3.1 原理

在单位时间内,无论事件触发多少次,只执行一次函数。触发再频繁,也严格按照固定频率执行。

javascript 复制代码
// 核心原理图
时间轴: 0ms---100ms---200ms---300ms---400ms---500ms
触发:   ↓  ↓↓  ↓↓↓  ↓  ↓↓   ↓  ↓
执行:   ↓       ↓       ↓       ↓  (每100ms执行一次)

3.2 手写节流函数(时间戳版)

javascript 复制代码
function throttle(fn, interval) {
  let lastTime = 0  // 上一次执行时间
  
  return function(...args) {
    const now = Date.now()
    
    // 距离上次执行已经超过间隔时间
    if (now - lastTime >= interval) {
      fn.apply(this, args)
      lastTime = now  // 更新上次执行时间
    }
  }
}

3.3 定时器版节流(最后一次也执行)

javascript 复制代码
function throttleTimer(fn, interval) {
  let timer = null
  
  return function(...args) {
    if (timer) return  // 有定时器说明还在冷却中
    
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
    }, interval)
  }
}

3.4 完整版节流(首尾都执行)

javascript 复制代码
function throttleFull(fn, interval) {
  let lastTime = 0
  let timer = null
  
  return function(...args) {
    const now = Date.now()
    const remaining = interval - (now - lastTime)
    
    // 如果超过间隔时间,立即执行
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      fn.apply(this, args)
      lastTime = now
    } else if (!timer) {
      // 否则设置定时器,确保最后一次也执行
      timer = setTimeout(() => {
        fn.apply(this, args)
        lastTime = Date.now()
        timer = null
      }, remaining)
    }
  }
}

3.5 使用示例

javascript 复制代码
// 滚动加载更多
function loadMore() {
  console.log('加载更多数据')
}

window.addEventListener('scroll', throttle(loadMore, 500))

// 鼠标移动跟随
function handleMouseMove(e) {
  console.log('鼠标位置:', e.clientX, e.clientY)
}

document.addEventListener('mousemove', throttle(handleMouseMove, 100))

四、完整代码示例(可直接运行)

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    .box {
      width: 300px;
      height: 200px;
      background: #f0f0f0;
      overflow-y: scroll;
      margin: 20px;
    }
    .long-content {
      height: 1000px;
    }
    button {
      margin: 10px;
      padding: 10px 20px;
    }
  </style>
</head>
<body>
  <h3>防抖示例:搜索框</h3>
  <input type="text" id="searchInput" placeholder="输入搜索内容" />
  <p id="searchResult">等待输入...</p>
  
  <h3>节流示例:滚动加载</h3>
  <div class="box" id="scrollBox">
    <div class="long-content">滚动我看看</div>
  </div>
  <p id="scrollResult">滚动次数:0</p>
  
  <h3>节流示例:按钮点击</h3>
  <button id="clickBtn">点击(节流)</button>
  <p id="clickResult">点击次数:0</p>

  <script>
    // ========== 防抖函数 ==========
    function debounce(fn, delay) {
      let timer = null
      return function(...args) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          fn.apply(this, args)
        }, delay)
      }
    }
    
    // ========== 节流函数(时间戳版) ==========
    function throttle(fn, interval) {
      let lastTime = 0
      return function(...args) {
        const now = Date.now()
        if (now - lastTime >= interval) {
          fn.apply(this, args)
          lastTime = now
        }
      }
    }
    
    // ========== 示例1:搜索框防抖 ==========
    let searchCount = 0
    function handleSearch(e) {
      searchCount++
      document.getElementById('searchResult').innerHTML = 
        `搜索:${e.target.value}(第${searchCount}次请求)`
    }
    const searchInput = document.getElementById('searchInput')
    searchInput.addEventListener('input', debounce(handleSearch, 500))
    
    // ========== 示例2:滚动节流 ==========
    let scrollCount = 0
    function handleScroll() {
      scrollCount++
      document.getElementById('scrollResult').innerHTML = 
        `滚动次数:${scrollCount}`
    }
    const scrollBox = document.getElementById('scrollBox')
    scrollBox.addEventListener('scroll', throttle(handleScroll, 200))
    
    // ========== 示例3:按钮节流 ==========
    let clickCount = 0
    function handleClick() {
      clickCount++
      document.getElementById('clickResult').innerHTML = 
        `点击次数:${clickCount}(实际点击更多,但被节流了)`
    }
    const clickBtn = document.getElementById('clickBtn')
    clickBtn.addEventListener('click', throttle(handleClick, 1000))
  </script>
</body>
</html>

五、适用场景详解

防抖(Debounce)适用场景

场景 说明 延迟时间建议
搜索框输入联想 用户停止输入后再搜索 300-500ms
窗口resize 停止拖动后再重新计算布局 200-300ms
表单输入验证 用户输完再验证 300-500ms
按钮防止重复提交 避免多次点击 立即执行版
滚动到底部加载 停止滚动后再判断 200ms

节流(Throttle)适用场景

场景 说明 间隔时间建议
滚动加载更多 滚动时按频率判断是否到底 200-500ms
鼠标移动跟随 控制跟随频率 16-30ms(约60fps)
拖拽元素 控制拖拽时的更新频率 16-30ms
游戏技能冷却 限制技能释放频率 按游戏设定
高频点击 限制按钮点击频率 500-1000ms

六、面试高频问题

Q1:防抖和节流的区别是什么?

答:

防抖:在事件频繁触发时,只执行最后一次。就像电梯等人,有人进来就重新计时,等人不进来了才关门。

节流:在事件频繁触发时,按固定频率执行。就像游戏技能冷却,CD到了才能放,CD期间按再多也没用。

简单说:防抖控制执行次数 (多次→1次),节流控制执行频率(固定间隔)。

Q2:手写一个防抖/节流函数

javascript 复制代码
// 防抖
function debounce(fn, delay) {
  let timer = null
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// 节流
function throttle(fn, interval) {
  let lastTime = 0
  return function(...args) {
    const now = Date.now()
    if (now - lastTime >= interval) {
      fn.apply(this, args)
      lastTime = now
    }
  }
}

Q3:防抖和节流分别适合什么场景?

答:

  • 防抖:搜索框输入、窗口resize、表单验证(最后一次才重要)

  • 节流:滚动加载、鼠标移动、拖拽、高频点击(需要控制频率)

相关推荐
xiaoyaohou1121 小时前
023、数据增强改进(二):自适应数据增强与AutoAugment策略
开发语言·python
鬼圣21 小时前
Python 上下文管理器
开发语言·python
程序员鱼皮21 小时前
又一个新项目开源,让 AI 帮你盯全网热点!
javascript·ai·程序员·编程·ai编程
MXN_小南学前端21 小时前
前端开发中 try...catch 到底怎么用?使用场景和最佳实践
javascript·vue.js
星空椰21 小时前
JavaScript 基础进阶:分支、循环与数组实战总结
开发语言·javascript·ecmascript
yong999021 小时前
IHAOAVOA:天鹰优化算法与非洲秃鹫优化算法的混合算法(Matlab实现)
开发语言·算法·matlab
小李子呢021121 小时前
前端八股---闭包和作用域链
前端
t***54421 小时前
有哪些常见的架构设计模式在现代C++中应用
开发语言·c++
IT_陈寒1 天前
Redis的内存溢出坑把我整懵了,分享这个血泪教训
前端·人工智能·后端
m0_738120721 天前
渗透测试基础ctfshow——Web应用安全与防护(五)
前端·网络·数据库·windows·python·sql·安全