浅谈js防抖和节流
在工作项目中我们肯定会遇到用户某个操作,会导致频繁触发我们的函数,
其实这并不是我们想要的,而且从性能上讲也是不可取的,有的可能还会导致bug。常见的有页面的滚动条滑动,按钮的点击·····那么能不能通过某个操作降低这些函数的触发频率?这就要用到防抖和节流了
防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
下面通过监听浏览器的滚动会频繁触发监听事件来说一下如何实现这两个函数
一个简单的监听滚动条事件的函数
let count = 0;
const showTop = () => {
let top = document.documentElement.scrollTop;
console.log(top, (count += 1));
};
window.onscroll = showTop;
点击滚动条的向下按钮我们会发现,监听函数执行了很多次,然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。
防抖(debounce)
基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:
- 如果在200ms内没有再次触发滚动事件,那么就执行函数
- 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时
效果:如果短时间内大量触发同一事件,只会执行一次函数。
实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:
/**
*
* @param callback 需要防抖的函数
* @param time 延时的时间
*/
const debounce = (callback: () => void, time: number) => {
let timer: NodeJS.Timeout | null = null;
console.log('timer', timer);
return function() {
if (timer) {
// 进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
clearTimeout(timer);
}
// 每次执行setTimeou() 都会返回一个计时器的编号,用timer保存这个编号,用于清除计时器重新计时
timer = setTimeout(callback, time);
console.log('timer2', timer);
};
};
// 然后是旧代码
let count = 0;
const showTop = () => {
let top = document.documentElement.scrollTop;
console.log(top, (count += 1));
};
window.onscroll = debounce(showTop,1000);
此时会发现,必须在停止滚动1秒以后,才会打印出滚动条位置。
到这里,已经把防抖实现了,现在给出定义:
对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。
节流(throttle)
但是如果某个用户闲着无聊一直不停的拖动滚动条,那么我们的事件是不是就一直不会被触发了,这个也不是我们想要的效果,这个时候我们可能会想要哪怕用户一直不停的触发,事件也能在特定的时间间隔内执行一次,这样是不是也能达到我们想要的效果。
原理大概就是:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。
效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
实现:这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:
/**
*
* @param callback 需要防抖的函数
* @param time 延时的时间
*/
const throttle = (callback: () => void, time: number) => {
let valid = true;
return function() {
if (!valid) {
return false;
}
valid = false;
setTimeout(() => {
callback(), (valid = true);
}, time);
};
};
// 然后是旧代码
let count = 0;
const showTop = () => {
let top = document.documentElement.scrollTop;
console.log(top, (count += 1));
};
window.onscroll = throttle(showTop,1000);
其他应用场景举例
讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:
- 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。
- 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)
原文:https://segmentfault.com/a/1190000018428170