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

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

相关推荐
z落落7 分钟前
C# 构造函数(无参/有参/重载/this)+析构函数(终结器)|GC 垃圾回收
java·开发语言·c#
卡卡军17 分钟前
🌈 react-sketch-ruler v3 升级之旅:当 React 遇上跨框架标尺引擎
前端·react.js
Asmewill22 分钟前
DeepAgents学习笔记三(Backend记忆存储)
前端
kkeeper~23 分钟前
0基础C语言积跬步之自定义类型结构体
c语言·开发语言
z落落33 分钟前
C# 字段与属性(get/set访问器、三种属性写法、只读属性)+属性拦截例子(get动态计算 + set数据校验)
开发语言·c#
Alan Lu Pop38 分钟前
前端开发助手
前端·智能体
程序员鱼皮40 分钟前
我用 GitHub 仓库养 AI 龙虾,自动开发上线项目!保姆级教程
前端·人工智能·ai·程序员·github·编程·ai编程
影寂ldy43 分钟前
C#栈和队列
开发语言·c#
SilentSamsara1 小时前
SQLAlchemy 2.x:异步 ORM 与数据库迁移 Alembic 完整指南
开发语言·数据库·python·sql·青少年编程·oracle·fastapi
27669582921 小时前
京东随机变速滑块拼图验证码识别(京东E卡)
java·服务器·前端·python·京东滑块·京东变速滑块·京东e卡绑卡