JS 防抖与节流
在前端开发的过程中,我们经常会需要绑定一些持续触发的事件,如 resize、scroll、mousemove 等等,但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数。
在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用 debounce(防抖)和 throttle(节流)的方式来减少调用频率,同时又不影响实际效果。
防抖和节流,都是控制事件触发频率的方法。应用场景有很多,输入框持续输入,将输入内容远程校验、多次触发点击事件、onScroll等等。
防抖(debounce)
函数防抖,这里的抖动就是执行的意思,而一般的抖动都是持续的,多次的。假设函数持续多次执行,我们希望让它冷静下来再执行。也就是当持续触发事件的时候,函数是完全不执行的,等最后一次触发结束的一段时间之后,再去执行。
需求分析:
- 思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms
如果在200ms内没有再次触发滚动事件,那么就执行函数
如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时 - 效果:如果短时间内大量触发同一事件,只会执行一次函数。
- 实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现
<!-- 点击事件调用 -->
<a href="javascript:void(0)" onclick="testDebounceHandler()">点我点我</a>
// 防抖调用、实现
testDebounceHandler = debounce(debounceTest, 1000)
let i = 0;
// 需要防抖的函数
function debounceTest() {
i ++;
console.log('防抖测试:' + i)
}
/**
* 防抖函数(debounce) 简单来说就是防止抖动。
* 触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
* @param [function] fn 需要防抖的函数
* @param [number] delay 毫秒,防抖期限值
*/
function debounce(fn, delay) {
var ctx;
var args;
var timer = null;
var later = function() {
fn.apply(ctx, args); // 一般是需要使用call或者apply来调用fn的来保持参数传递
// 当事件真正执行后,清空定时器
timer = null;
};
return function() {
ctx = this;
args = arguments;
// 当持续触发事件时,若发现事件触发的定时器已设置时,则清除之前的定时器
if (timer) {
clearTimeout(timer);
timer = null;
}
// 重新设置事件触发的定时器
timer = setTimeout(later, delay);
};
}
节流(throttle)
函数节流,当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流的意思是让函数有节制地执行,而不是毫无节制的触发一次就执行一次。什么叫有节制呢?就是在一段时间内,只执行一次。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。
需求分析:
- 思路:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。
- 效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
- 实现:借助 setTimeout + 当前时间戳,用时间间隔去控制,记录上一次函数的执行时间,与当前时间作比较,如果当前时间与上次执行时间的时间差大于一个值,就执行
// 使用setInterval实现节流测试
let j = 0;
const interval = setInterval(() => {
console.log(`${j} --- 进来了,但是不知道有没有执行`);
testThrottleHandler(j++);
}, 1000);
// 节流调用、实现
const testThrottleHandler = throttle(throttleTest, 5000);
// 需要节流的函数
function throttleTest(arg) {
console.log(`节流测试--${arg}--被执行了`);
}
/**
* 节流(throttle) 当持续触发事件时,保证隔间时间触发一次事件。
* @param [function] fn 需要节流的函数
* @param [number] delay 延迟执行毫秒数
*/
function throttle(fn, delay) {
var ctx;
var args;
// 记录上次触发事件
var previous = Date.now();
var later = function() {
fn.apply(ctx, args); // 一般是需要使用call或者apply来调用fn的来保持参数传递
};
return function() {
ctx = this;
args = arguments;
var now = Date.now();
// 本次事件触发与上一次的时间比较
var diff = now - previous - delay;
// 如果隔间时间超过设定时间,即再次设置事件触发的定时器
if (diff >= 0) {
// 更新最近事件触发的时间
previous = now;
setTimeout(later, delay);
}
};
}
总结
防抖和节流巧妙地用了setTimeout,来控制函数执行的时机,优点很明显,可以节约性能,不至于多次触发复杂的业务逻辑而造成页面卡顿。
- 函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
- 函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
- 区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。