一、核心区别速览
| 对比维度 | 防抖(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、表单验证(最后一次才重要)
节流:滚动加载、鼠标移动、拖拽、高频点击(需要控制频率)