JavaScript防抖与节流:原理、实现及应用场景详解
本文深入解析JavaScript中防抖(debounce)与节流(throttle)的核心原理及实现方式。通过延迟与前缘两种模式的对比,结合代码示例,详细阐述两者在减少服务器请求、提升页面性能方面的具体应用场景,帮助开发者掌握控制函数执行频率的关键技术。
1. 前言
首先举例说明。在百度首页的搜索框中,用户输入时,百度服务器会实时返回联想关键词。每修改一个字,联想词就会更新一次,这是用户可见的速度。实际上,若不加以控制,可能已向服务器发起数十次相同的联想请求,具体频率取决于不同PC的运行速度。正如前述,对同一关键词发起多次请求,用户可能只需一次结果,其余均为无效请求。若成千上万甚至上亿用户同时请求,将给服务器带来巨大负担。
防抖节流解决的问题:
在高频连续触发的事件中,若不加控制地频繁调用事件处理函数,会显著增加浏览器或服务器的负载,进而影响用户体验。常见的连续触发场景包括:
例如浏览器滚动条的滚动事件(scroll)、窗口大小调整事件(resize)、输入框的内容校验以及移动端的触摸移动事件(touchmove)等。
为了解决上述问题,我们通常采用防抖函数(debounce)和节流函数(throttle)来限制事件处理函数的调用频率。
总体而言,防抖和节流都是在时间轴上对函数的执行次数进行控制的手段。
2. 函数防抖(debounce)
延迟防抖
延迟防抖(debounce)的定义是:当事件被触发后,等待n秒再执行回调;如果在这n秒内事件再次被触发,则重新计时。
生活中的例子是电梯:有人进入电梯(触发事件),电梯将在10秒后启动(执行监听器);如果在这10秒内又有人进入(再次触发),电梯将重新等待10秒后启动(重新计时)。
当持续触发事件时,必须保证在一段时间内没有再次触发事件,事件处理函数才会执行一次。
如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
如下图所示,当持续触发click事件时,handle函数并不会立即执行;只有当1000毫秒内没有再次触发click事件时,才会延时执行click事件。

前缘防抖
前缘防抖的策略是:先执行动作,然后设定一个周期;在周期内有事件被触发时,不执行动作,且周期重新设定。
为什么要这样设计呢?
考虑第一种延迟防抖的情况:我们原本希望降低对用户输入关键字发起联想请求的频率。但如果用户在设定的时间内持续输入,用户将一直看不到返回结果。相比之下,第一次输入时立即发起请求并展示结果,后续输入在停止后继续请求,体验会更佳。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖</title>
</head>
<body>
<button id="debounce1">点我防抖呐!</button>
<script>
function handle() {
console.log("防抖成功!");
}
window.onload = function() {
// 1. 获取按钮,并绑定事件
var myDebounce = document.getElementById('debounce1');
myDebounce.addEventListener('click',debounce(handle,1000,true));
}
// 防抖函数
function debounce(fn,wait, immediate ){
//2. 设置时间戳,使用setTimeout让返回函数延迟执行
let timer, result;
return function(...args){
// 3. timer存在,将定时器中的函数清除
if(timer) clearTimeout(timer);
// 4.1 立即执行返回函数
if(immediate){
if(!timer){
result = fn.apply(this,args);
}
timer = setTimeout(() => {
timer = null;
},wait);
}else{ // 4.2 非立即执行返回函数
timer = setTimeout(() => {
fn.apply(this,args);
},wait);
}
}
// 5. 立即执行时返回函数的返回值
return result;
}
</script>
</body>
</html>
实现效果:

原理解析:
- 防抖函数的作用是对传入的函数进行延时包装后返回。
- 当setTimeout在前一次未执行完前,第二次触发将会覆盖掉前面的定时器,从而执行第二次的功能。
- 由于前一次调用因异步加延时还未执行完,使用clearTimeout清除前面的定时器,以取消上次的fn功能。
- 为了保持fn内部this的指向,使用apply改变this指向。
- 注意fn传入的是函数本身,而不是函数的调用。
防抖函数实现总结
function debounce(fn,wait, immediate ){
//2. 设置时间戳,使用setTimeout让返回函数延迟执行
let timer, result;
return function(...args){
// 3. timer存在,将定时器中的函数清除
if(timer) clearTimeout(timer);
// 4.1 立即执行返回函数
if(immediate){
if(!timer){
result = fn.apply(this,args);
}
timer = setTimeout(() => {
timer = null;
},wait);
}else{ // 4.2 非立即执行返回函数
timer = setTimeout(() => {
fn.apply(this,args);
},wait);
}
}
// 5. 立即执行时返回函数的返回值
return result;
}
3. 函数节流(throttling)
throttling(节流)的策略是:在固定周期内,只执行一次动作;若有新事件触发,不执行。周期结束后,若有新事件触发,则开始新的周期。节流策略同样分为前缘和延迟两种模式。
与debounce类似,延迟模式指周期结束后执行动作,前缘模式指执行动作后再开始周期。
- 节流会稀释函数的执行频率。
- 在持续触发事件的过程中,函数会立即执行,并且每n秒执行一次。
生活中的例子:目前有一种说法是,当1秒内连续播放24张以上的图片时,在人眼的视觉中就会形成一个连贯的动画。因此,电影播放(以前是,现在不知道)基本以每秒24张的速度播放。如果不采用24张而采用100张,虽然画面更流畅,但会显得非常浪费资源。
延迟节流

前缘节流

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流</title>
</head>
<body>
<button id="debounce1">点我节流呐!</button>
<script>
function handle() {
console.log("节流成功!");
}
window.onload = function() {
var myDebounce = document.getElementById('debounce1');
myDebounce.addEventListener('click',throttling(handle,1000,false));
}
// 节流函数
function throttling(fn,wait,immediate){
let timer;
return function(...args) {
if(!timer){
if(immediate){
fn.apply(this,args);
}
timer = setTimeout(() => {
if(!immediate) {
fn.apply(this,args);
}
timer = null;
},wait);
}
}
}
</script>
</body>
</html>
实现效果:

节流函数实现总结
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流</title>
</head>
<body>
<button id="debounce1">点我节流呐!</button>
<script>
function handle() {
console.log("节流成功!");
}
window.onload = function() {
var myDebounce = document.getElementById('debounce1');
myDebounce.addEventListener('click',throttling(handle,1000,false));
}
// 节流函数
function throttling(fn,wait,immediate){
let timer;
return function(...args) {
if(!timer){
if(immediate){
fn.apply(this,args);
}
timer = setTimeout(() => {
if(!immediate) {
fn.apply(this,args);
}
timer = null;
},wait);
}
}
}
</script>
</body>
</html>
4. 两者区别
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。
函数防抖只是在最后一次事件后才触发一次函数。
例如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次Ajax请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
5. 应用场景
对于函数防抖,有以下几种应用场景:
- 给按钮加函数防抖防止表单多次提交。
- 对于输入框连续输入进行AJAX验证时,用函数防抖能有效减少请求次数。
- 判断scroll是否滑到底部,滚动事件+函数防抖
总的来说,适合多次事件一次响应的情况
对于函数节流,有如下几个场景:
- 游戏中的刷新率
- DOM元素拖拽
- Canvas画笔功能
总的来说,适合大量事件按时间做平均分配触发。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注本站的更多内容!
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
JavaScript函数定义的三种方式:声明、表达式与箭头函数
2026-11-05 13:50:55
