JavaScript 中防抖和节流的实现原理及应用场景
前言
开发中,经常会遇到以下场景:监听鼠标移动 onmousemove,监听页面滚动 onscroll,监听 input 输入,按钮搜索、提交功能的点击事件等。这些场景下,事件会被频繁触发,但我们并不想事件被频繁触发,这时就需要通过防抖和节流来限制频繁操作。
防抖和节流都是为了解决事件频繁触发的问题,都是为了降低资源的浪费,提高性能。 但在实现原理上有些不同,具体实现原理以及防抖和节流之间到底有什么样的区别,详见下文。
一、 防抖( debounce )
定义: 在规定时间内持续触发事件,那么这个延时会被重置,不会执行函数内的事件处理函数,当在规定时间内不会再次触发该事件,则执行函数内的事件处理函数。也就是说,当用户一直触发该事件,且每次触发的事件间隔均小于规定延时,那么最终也只会执行一次最后一次的函数回调。
使用场景: 防抖的主要使用场景就是搜索框持续监听用户输入的内容。如果不使用防抖进行优化,那么会出现一个现象,就是当用户快速输入十个搜寻字符,对于用户来说,前九次搜索并不是用户想要的结果,但是服务器仍会重新发起九次请求,这并不是我们想要的。使用防抖优化后,前九次快速输入的请求,会被防抖函数拦截,只会在用户输入最后一个字符并等待了一段时间后(设定的防抖延时),只会发起最后一次请求。
防抖函数的实现:
// 1.封装防抖函数 **防抖重在清零 clearTimeout(timer)**
function debounce(fn, time) {
// 4.创建一个标记用来存放定时器的返回值
let timeout = null;
return function () {
// 5.每当用户触发input事件 把前一个 setTimeout 清楚掉
clearTimeout(timeout);
// 6.然后又创建一个新的 setTimeout, 这样就能保证输入字符后等待的间隔内 还有字符输入的话,就不会执行 setTimeout里面的内容
timeout = setTimeout(() => {
// 7.这里进行防抖的内容
fn();
}, time);
};
}
// 2.获取inpt元素
var inp = document.getElementById("inp");
// 8. 测试防抖临时使用的函数
function sayHi() {
console.log("防抖成功");
}
// 3.给inp绑定input事件 调用封装的防抖函数 传入要执行的内容与间隔事件
inp.addEventListener("input", debounce(sayHi, 5000));
二、节流( throttle )
定义: 在持续触发的事件中,在规定延时内只执行一次事件处理函数。如果在规定延时内再次触发则会被忽略。节流的目的就是将频繁触发的事件转变为少量触发,降低服务器处理的频率以提高效率。
使用场景: 节流的使用场景,常见于一些频繁触发的业务,例如移动端页面的上拉刷新和下拉加载,搜索提交按钮的点击事件等一些频繁触发的使用场景。节流函数的使用本质上就是限制事件处理函数的处理次数,降低服务器使用频率,以实现优化性能。
节流函数的实现:
// 1.封装节流函数(使用定时器) 节流重在加锁 timer=timeout
function throttle(fn, delay) {
//3. 通过闭包保存一个 "节流阀" 默认为false
let timer = false;
return function () {
//8.触发事件被调用 判断"节流阀" 是否为true 如果为true就直接trurn出去不做任何操作
if (timer) {
return;
} else {
//4. 如果节流阀为false 立即将节流阀设置为true
timer = true; //节流阀设置为true
//5. 开启定时器
setTimeout(() => {
//6. 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
//7. 最后在setTimeout执行完毕后再把标记'节流阀'为false(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是true,在开头被return掉
timer = false;
}, delay);
}
};
}
三、总结
1.防抖 防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。代码实现重在清零 clearTimeout。
2.节流 控制流量,单位时间内事件只能触发一次,与服务器端的限流 (Rate Limit) 类似。代码实现重在开锁关锁 timer=timeout; timer=null。