引言:为什么需要节流和防抖?
在Web开发中,我们经常需要处理高频触发的事件(如resize、scroll、mousemove、input等)。如果每次事件触发都立即执行函数,会导致以下问题:
-
性能问题:频繁的函数调用会消耗大量CPU资源
-
网络压力:如搜索框输入时频繁发起请求
-
用户体验差:页面卡顿、动画不流畅

一、核心概念深度解析
1.1 防抖(Debounce)原理剖析
核心思想:确保一个函数在事件被触发后,只在最后一次触发后延迟执行。
实现细节: javascript function debounce(fn, delay = 300) { let timer = null; return function(...args) { const context = this; clearTimeout(timer); // 清除之前的定时器 timer = setTimeout(() => { fn.apply(context, args); // 在延迟后执行 }, delay); }; }
function debounce(fn, delay = 300) {
let timer = null
return function (...args) {
const context = this
clearTimeout(timer)
// 清除之前的定时器
timer = setTimeout(() => {
fn.apply(context, args)
// 在延迟后执行
}, delay)
}
}
关键点:
-
每次事件触发都会清除之前的定时器
-
只在最后一次触发后延迟执行
-
适合"事件结束"后才需要处理的场景

1.2 节流(Throttle)原理剖析
核心思想:确保一个函数在事件被触发后,在固定时间间隔内只执行一次。
实现细节: javascript function throttle(fn, delay = 300) { let lastCall = 0; return function(...args) { const now = Date.now(); if (now - lastCall >= delay) { fn.apply(this, args); lastCall = now; } }; }
function throttle(fn, delay = 300) {
let lastCall = 0
return function (...args) {
const now = Date.now()
if (now - lastCall >= delay) {
fn.apply(this, args)
lastCall = now
}
}
}
关键点:
-
记录上次执行时间
-
只有超过时间间隔才会再次执行
-
适合"持续触发"但需要控制频率的场景

1.3 可视化对比


二、高级实现与优化
2.1 带立即执行选项的防抖
function debounce(fn, delay, immediate = false) {
let timer = null
return function (...args) {
const context = this
clearTimeout(timer)
if (immediate) {
if (!timer) {
fn.apply(context, args)
timer = setTimeout(() => {
timer = null
}, delay)
}
} else {
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
}
使用场景:
-
immediate=true:第一次立即执行,后续防抖 -
immediate=false:完全防抖
2.2 带leading和trailing选项的节流
function throttle(fn, delay, options = {}) {
let lastCall = 0
let timer = null
return function (...args) {
const now = Date.now()
const elapsed = now - lastCall
if (!options.hasOwnProperty('leading') || options.leading) {
if (!timer) {
fn.apply(this, args)
lastCall = now
return
}
}
if (elapsed > delay) {
fn.apply(this, args)
lastCall = now
clearTimeout(timer)
timer = null
} else if (!options.hasOwnProperty('trailing') || options.trailing) {
clearTimeout(timer)
timer = setTimeout(() => {
if (timer) {
fn.apply(this, args)
lastCall = now
clearTimeout(timer)
timer = null
}
}, delay - elapsed)
}
}
}
选项说明:
-
leading:是否在开始时立即执行 -
trailing:是否在结束时执行最后一次
2.3 使用requestAnimationFrame优化节流
function throttle(fn, delay) {
let lastCall = 0
return function (...args) {
const now = Date.now()
if (now - lastCall >= delay) {
requestAnimationFrame(() => {
fn.apply(this, args)
lastCall = now
})
}
}
}
优势:
-
与浏览器刷新率同步
-
更适合动画场景

三、实战案例深度解析
3.1 搜索框优化(防抖)
const searchInput = document.getElementById('search')
const searchResults = document.getElementById('results')
// 原始实现(有问题)
searchInput.addEventListener('input', function () {
fetch(`/api/search?q=${this.value}`)
.then((response) => response.json())
.then((data) => {
searchResults.innerHTML = data.results.map((item) => <li>${item.name}</li>).join('')
})
})
// 优化后(使用防抖)
const debouncedSearch = debounce(function () {
fetch(`/api/search?q=${this.value}`)
.then((response) => response.json())
.then((data) => {
searchResults.innerHTML = data.results.map((item) => <li>${item.name}</li>).join('')
})
}, 300)
searchInput.addEventListener('input', debouncedSearch)
优化点:
-
减少不必要的网络请求
-
提升用户体验
-
降低服务器压力
3.2 无限滚动加载(节流)
window.addEventListener('scroll', function () {
const { scrollTop, clientHeight } = document.documentElement
const scrollHeight = document.documentElement.scrollHeight
if (scrollTop + clientHeight >= scrollHeight - 100) {
loadMoreContent()
}
})
// 原始实现(有问题)
function loadMoreContent() { // 实际加载逻辑 }
// 优化后(使用节流)
const throttledLoad = throttle(function () {
const { scrollTop, clientHeight } = document.documentElement
const scrollHeight = document.documentElement.scrollHeight
if (scrollTop + clientHeight >= scrollHeight - 100) {
loadMoreContent()
}
}, 300)
window.addEventListener('scroll', throttledLoad)
优化点:
-
避免滚动时频繁触发加载
-
保持页面流畅性
-
合理控制加载频率
3.3 拖拽排序(防抖+节流结合)
const draggableItems = document.querySelectorAll('.draggable-item')
let isDragging = false
// 使用节流处理 mousemove
const throttledMove = throttle(function (e) {
if (!isDragging) return
// 更新元素位置
}, 16) // 约60fps
// 使用防抖处理mouseup const debouncedEnd = debounce(function() { isDragging = false; // 执行排序逻辑 }, 100);
draggableItems.forEach((item) => {
item.addEventListener('mousedown', startDrag)
})
function startDrag(e) {
isDragging = true
// 初始化拖拽位置
}
document.addEventListener('mousemove', throttledMove)
document.addEventListener('mouseup', debouncedEnd)
优化点:
-
拖拽过程使用节流保证流畅性
-
结束拖拽使用防抖确保准确识别
-
结合两种技术优势

四、常见问题与解决方案
4.1 内存泄漏问题
问题:在组件销毁时未清理定时器
解决方案:
class MyComponent {
constructor() {
this.debouncedFn = debounce(this.handleEvent, 300)
}
componentWillUnmount() {
// 清理定时器
this.debouncedFn.cancel = () => {
/* 清理逻辑 */
}
}
}
4.2 this指向问题
问题:在类方法中使用时this指向错误
解决方案:
// 使用箭头函数
class MyClass {
handleEvent = debounce(() => {
// 这里的this指向正确
}, 300)
}
// 或使用bind
class MyClass {
handleEvent() {
// 方法逻辑
}
constructor() {
this.debouncedFn = debounce(this.handleEvent.bind(this), 300)
}
}
4.3 参数传递问题
问题:使用闭包时参数传递错误
解决方案:
function debounce(fn, delay) {
let timer = null
return function (...args) {
// 使用剩余参数语法传递所有参数
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
// 正确传递参数
}, delay)
}
}

五、性能测试与优化建议
5.1 性能测试方法
// 测试防抖性能
const start = Date.now()
for (let i = 0; i < 1000; i++) {
debouncedFn()
}
console.log('防抖测试耗时:', Date.now() - start)
// 测试节流性能
const start2 = Date.now()
for (let i = 0; i < 1000; i++) {
throttledFn()
}
console.log('节流测试耗时:', Date.now() - start2)
5.2 优化建议
-
合理设置延迟时间:
-
防抖:200-500ms(根据场景调整)
-
节流:100-300ms(根据性能需求调整)
-
-
根据场景选择技术:
-
需要"事件结束"后处理 → 防抖
-
需要"持续处理"但控制频率 → 节流
-
-
结合requestAnimationFrame:
-
对于动画相关操作,使用节流+requestAnimationFrame
-
-
考虑移动端差异:
-
移动端可能需要更长的延迟时间
-
注意touch事件与mouse事件的差异
-
结语
节流和防抖是前端性能优化的基石技术,掌握它们能显著提升应用质量。通过本文的学习,你应该能够:
-
深入理解两者的核心原理和差异
-
掌握多种实现方式和优化技巧
-
知道在哪些具体场景下应用
-
避免常见问题和陷阱
建议在实际项目中积极应用这些技术,并通过性能分析工具验证优化效果。随着经验积累,你将能更精准地选择和使用这些技术,构建更流畅、更高效的前端应用。

6万+

被折叠的 条评论
为什么被折叠?



