JavaScript事件防抖与节流的核心实现与应用场景解析
在处理scroll、resize等高频触发事件时,若直接绑定处理函数,极易引发浏览器性能问题。通过引入防抖(Debounce)和节流(Throttle)机制,可以显著降低函数执行频率。本文将深入解析这两者的核心差异,并提供从基础到高级的完整代码实现,涵盖立即执行、定时器延迟及混合模式,适用于搜索联想、无限滚动加载等多种前端场景。
防抖动(Debounce)的实现
防抖动的主要策略是:规定一个时间间隔,在指定时间内如果再次触发事件,则重新计时。只有当事件停止触发并经过这段延迟后,回调函数才会执行一次。这类似于“合并多次操作为一次”,特别适用于需要等待用户完成连续动作后才响应的场景(如搜索输入框联想)。
第1步:基础防抖实现
目的是将连续的事件调用合并为最后一次调用。通过维护一个定时器,每次事件触发都清除旧定时器并设置新定时器。
// 将会包装事件的 debounce 函数
function debounce(fn, delay) {
// 维护一个 timer
let timer = null;
return function() {
// 通过 ‘this' 和 ‘arguments' 获取函数的作用域和变量
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
}
}
使用示例:当用户滚动容器时,每隔一定时间只记录一次状态。
// 当用户滚动时被调用的函数
function foo() {
console.log('You are scrolling!');
}
// 在 debounce 中包装我们的函数,过 2 秒触发一次
let elem = document.getElementById('container');
elem.addEventListener('scroll', debounce(foo, 2000));
第2步:支持立即执行的防抖
在某些场景下,我们希望第一次触发时就立即执行,之后如果在延迟期内继续触发则忽略,直到停止触发后再等待延迟周期方可下一次执行。这需要增加 immediate 参数。
function debouce(func,delay,immediate){
var timer = null;
return function(){
var context = this;
var args = arguments;
if(timer) clearTimeout(timer); //注意原稿此处变量名为time,已修正为timer
if(immediate){
//根据距离上次触发操作的时间是否到达delay来决定是否要现在执行函数
var doNow = !timer;
//每一次都重新设置timer,就是要保证每一次执行的至少delay秒后才可以执行
timer = setTimeout(function(){
timer = null;
},delay);
//立即执行
if(doNow){
func.apply(context,args);
}
}else{
timer = setTimeout(function(){
func.apply(context,args);
},delay);
}
}
}
节流(Throttle)的实现
节流的作用是:无论事件触发多频繁,保证在规定时间内一定执行且只执行一次真正的事件处理函数。与防抖不同,节流强调的是“固定频率执行”。它常用于页面的无限加载场景,确保用户在滚动时每隔一段时间发一次Ajax请求,而不是只在停下时请求。
第3步:基于时间戳的节流
思路是记录上一次执行的时间 prev,每次触发时计算当前时间 now。如果差值大于延迟时间 delay,则执行函数并更新 prev。
var throttle = function(func,delay){
var prev = Date.now();
return function(){
var context = this;
var args = arguments;
var now = Date.now();
if(now-prev>=delay){
func.apply(context,args);
prev = Date.now();
}
}
}
这种实现的优点是:当高频事件触发时,如果首次触发的间隔足够大,会立即执行;后续每 delay 秒执行一次。缺点是:最后一次触发后不会立即执行,而是可能在一段空闲期后才被处理。
第4步:基于定时器的节流
思路是利用定时器标志位。如果定时器存在,说明正处于冷却期,不执行;如果定时器不存在,则开启定时器并执行逻辑,随后清空定时器以允许下一次触发。
var throttle = function(func,delay){
var timer = null;
return function(){
var context = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
func.apply(context,args);
timer = null;
},delay);
}
}
}
这种实现的缺点是:第一次触发时不会立即执行,而是等待 delay 秒后才执行;结束时可能会多执行一次。适合对实时性要求不高,但要求执行频率上限严格的场景。
第5步:定时器与时间戳混合节流
为了兼顾“立即执行”和“结束前再执行一次”,可以综合使用两种方案。计算剩余时间 remaining,如果未到时间则设定定时器,到了时间则立即执行并重置起始时间。
var throttle = function(func,delay){
var timer = null;
var startTime = Date.now();
return function(){
var curTime = Date.now();
var remaining = delay-(curTime-startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if(remaining<=0){
func.apply(context,args);
startTime = Date.now();
}else{
timer = setTimeout(func,remaining);
}
}
}
该实现确保了在每个 delay 时间窗口内一定会执行一次函数,且在事件流结束时能处理最后一次延迟逻辑,是较为严谨的节流方案。
总结与对比
防止一个事件频繁触发回调函数的方式主要分为两类:
- 防抖动(Debounce):将几次操作合并为一次操作。原理是维护一个计时器,规定在
delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。结果是只有最后一次操作能被触发。 - 节流(Throttle):使得一定时间内只触发一次函数。不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。而防抖动只是在最后一次事件后才触发一次函数(指非立即模式)。其原理是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器或更新基准时间。
以上就是JavaScript防抖动与节流处理的详细内容,更多关于JavaScript性能优化的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









