节流(throttle):给频繁操作加上冷却时间!🔥
在游戏里,你放了一个大招,是不是要等冷却时间结束后才能再次释放?在 JavaScript 的世界里,也有一种技术叫做"节流",它就像给技能的释放加上了冷却时间,避免频繁操作导致页面卡顿甚至崩溃。今天,我们就来聊聊节流的底层机制,它和防抖(debounce)的区别,以及为什么我们需要它。
当事件变得"太热情" ❤️🔥
想象一下,你在一个搜索框中输入关键词,页面会根据输入内容实时联想并展示结果。看起来很方便,对吧?但是,如果用户输入速度很快,那么每按一次键都会触发一次网络请求,这可能会导致:
- 大量请求在短时间内发出,服务器压力剧增
- 请求返回顺序不一致,导致页面展示混乱
- 浏览器性能下降,页面卡顿甚至崩溃
再比如,你在滚动页面时,需要计算某些元素的位置(比如懒加载图片),如果每次滚动都触发,那么滚动一下可能会触发几十次事件!
这时候,我们就需要两种技术来优化:防抖(debounce) 和 节流(throttle) 。在防抖(Debounce)的底层机制:闭包与this指向的深度解析 🚀防抖(Debounce)的底层机制:闭包与this - 掘金这篇文章中已经介绍过防抖了,今天,我们重点讲解节流。
节流的基本概念 ⏱️
节流(throttle)的核心思想是:固定时间间隔内只执行一次操作。也就是说,无论事件触发多么频繁,执行函数都会按照固定的时间间隔执行一次。

节流的底层机制 🧠
让我们通过一个具体的例子来理解节流的实现。以下是一个常见的节流函数实现:
javascript
function throttle(fn, delay) {
let last; // 上一次执行的时间戳
let deferTimer; // setTimeout的ID
return function(...args) {
let that = this;
let now = +new Date(); // 当前时间戳
// 如果上次执行时间存在,且当前时间小于上次执行时间+延迟时间(即还在冷却中)
if (last && now < last + delay) {
clearTimeout(deferTimer); // 清除之前设置的定时器
deferTimer = setTimeout(function() {
last = now; // 更新上一次执行时间为当前时间
fn.apply(that, args); // 执行函数
}, delay);
} else {
// 第一次执行或者冷却时间已过
last = now;
fn.apply(that, args);
}
};
}
代码解析:
-
闭包保存状态 :
last
和deferTimer
被闭包保存,用于记录上一次执行的时间和定时器ID。 -
返回函数 :当我们调用
throttle(ajax, 2000)
时,它返回一个新的函数(即throttleAjax
),这个函数被绑定到事件监听器上。 -
时间戳判断 :每次事件触发时,获取当前时间戳
now
,并与上一次执行的时间戳last
进行比较:- 如果距离上次执行的时间小于设定的间隔
delay
,说明还在冷却期 - 否则,说明已经过了冷却期,可以立即执行
- 如果距离上次执行的时间小于设定的间隔
-
定时器的作用 :在冷却期内,每次事件触发都会重置定时器,保证在最后一次事件触发后的
delay
时间后执行一次函数。
这样,我们就实现了:在单位时间内,最多执行一次函数。
节流 vs 防抖:区别在哪? 🥊
这张图片形象地表示了地表示了debounce
和throttle
时间触发的频率的区别


很多人容易混淆节流和防抖,它们虽然都是用来控制函数执行频率的,但行为不同:
特性 | 节流(throttle) | 防抖(debounce) |
---|---|---|
执行时机 | 固定时间间隔执行一次 | 事件停止触发后延迟执行 |
执行次数 | 单位时间内至少执行一次 | 只执行最后一次触发的事件 |
适用场景 | 滚动事件、窗口调整、输入联想 | 搜索框输入验证、表单提交 |
类比 | 技能冷却时间,固定间隔释放 | 电梯关门,等待最后一个人进入 |
用一个生活场景比喻:
- 防抖:电梯门要等最后一个人进入后,等待一段时间(比如5秒)再关门
- 节流:电梯门每隔10秒自动尝试关闭一次,如果有人进入就重新等待,但每隔10秒总会尝试关闭
为什么我们需要节流? 💡
节流的主要目的是优化性能 和避免不必要的资源浪费。具体来说:
-
减少函数执行频率:对于频繁触发的事件(如滚动、鼠标移动、窗口调整大小),节流可以大幅减少事件处理函数的执行次数,从而减轻浏览器负担。
-
保证用户体验:在搜索联想场景中,我们可能不需要每次按键都发送请求,而是每隔一定时间发送一次,这样既能减少请求数量,又能保证用户看到实时反馈。
-
避免请求混乱:在输入联想时,如果每次按键都发送请求,那么请求返回的顺序可能不一致,导致最终展示的结果不是最新的。通过节流,我们可以确保请求按固定间隔发送,减少混乱。
实际应用场景 🚀
1. 滚动事件优化
javascript
// 监听滚动事件,但最多每100ms执行一次
window.addEventListener('scroll', throttle(function() {
// 计算元素位置、懒加载图片等
}, 100));
2. 窗口调整大小
javascript
// 窗口调整大小时重新布局,但避免过于频繁
window.addEventListener('resize', throttle(function() {
// 重新计算布局
}, 200));
3. 输入联想搜索
javascript
// 搜索框输入联想,每500ms发送一次请求
input.addEventListener('input', throttle(function(e) {
// 发送搜索请求
fetchResults(e.target.value);
}, 500));
4. 游戏中的技能释放
javascript
// 防止玩家连续点击技能按钮
attackButton.addEventListener('click', throttle(function() {
// 释放技能
castSkill();
}, 1000)); // 技能冷却时间1秒
完整代码示例 🧩
下面是文章开头提供的完整代码,实现了输入框的节流功能:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流示例</title>
</head>
<body>
<input type="text" id="inputC"/>
<script>
let inputC = document.getElementById('inputC')
const ajax = function(content){//被节流的函数
// 模拟网络请求
console.log('ajax request: ' + content)
}
// 节流函数
function throttle(fn,delay){
let last,//上一次执行的时间
deferTimer;//timeout id
return function(...args){
let that = this;
let now = +new Date();//当前时间戳
if(last && now <last + delay){
clearTimeout(deferTimer);//清除定时器
deferTimer = setTimeout(function(){
last = now;
fn.apply(that,args)
},delay)
}else{
last = now;
fn.apply(that,args)
}
}
}
// 创建节流版本的ajax函数
let throttleAjax = throttle(ajax, 500)
// 监听输入框的keyup事件
inputC.addEventListener('keyup',function(e){
throttleAjax(e.target.value)
})
</script>
</body>
</html>

在这个例子中,无论用户输入多快,ajax
函数至少会每500ms执行一次(防抖:只要用户输入的足够快,函数可能很久都不会执行)。这样,我们就有效地控制了请求频率。
总结 🎯
节流是一种非常实用的性能优化技术,它通过固定时间间隔内只执行一次函数,有效控制了函数执行的频率。与防抖不同,节流保证了在单位时间内至少执行一次,适用于需要持续反馈的场景(如滚动事件)。
在实际开发中,我们应该根据具体需求选择使用节流还是防抖:
- 如果你关心最终状态,比如输入结束后的验证,使用防抖
- 如果你需要固定间隔执行,比如滚动事件,使用节流
最后,希望你在项目中合理使用节流技术,为你的Web应用加上"冷却时间",让它运行得更流畅!🚀