前端八股性能优化(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、表单验证(最后一次才重要)

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

相关推荐
周杰伦fans几秒前
轻量模型高并发推理优化技巧
开发语言·人工智能·c#
广州华水科技4 分钟前
水库北斗GNSS变形监测系统及单北斗形变监测一体机应用解析
前端
Frank_refuel4 分钟前
【C++项目】手撕JRpc框架(单机怎么优化到分布式思想)
开发语言·c++
可乐鸡翅yeah_22 分钟前
fmp4 碎片 MP4 格式 HLS 流,和传统 TS 分片有什么不一样
开发语言·javascript·ios·音视频·safari·m3u8·m3u8在线播放
流形填表25 分钟前
多校区题库标准化:Excel模板与校验脚本
开发语言·python
步行cgn34 分钟前
AOP 通知的类型详解
开发语言
weixin_3077791336 分钟前
从“人操作仪器”到“Agent 自主发现”:OPL 金属材料自驱动实验室 MVP 技术论述
开发语言·人工智能·算法·架构
笑鸿的学习笔记1 小时前
C++笔记之SIMD与SSE指令集
开发语言·c++·笔记
anew___1 小时前
《从零手写操作系统 (20):ELF加载器——让OS读懂现代编译器》
java·服务器·前端
ss2731 小时前
AI全栈实战 | 2.3-01 组件库实战:Element Plus 怎么搭出专业界面,过度封装为什么成维护负担
前端·javascript·elementui·vue