函数防抖 和 函数节流 (debounce and throttle)

在前端开发过程中,我们经常会绑定一些事件,例如click、resize、scroll事件,但是这些事件无法控制触发频率,甚至如果用户不停的点击按钮,click事件也会频繁的触发,事件的触发频率变高之后,响应速度也会下降,更可能出现出现页面卡顿,假死现象。
针对这种问题,有两种解决策略,也就是 函数防抖 和 函数节流 (debounce and throttle)

debounce

debounce: 去抖动,防止误动作,防抖动。
其主要思想是:当一个事件触发时,为其设置一个周期用来延期执行动作,如果周期内再次触发则重新设定周期,循环往复直到周期结束之后才执行 要执行的动作。
比如又一个按钮,如果用户点击了多次,会触发多次事件,但是使用了debounce处理之后,事件只会在周期内用户点击的最后一次才执行。
这里有三种办法,可以根据实际情况使用。

第一种 n秒后执行

每触发一次事件,生成一个定时器,清空之前的定时器,定时器到达时间才会执行函数。

1
2
3
4
5
6
7
8
9
10
11
function debounce(fn, delay) {
let timer; // 通过闭包保存定时器变量
return function () {
let that = this;
let args = arguments;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(that, args)
}, delay);
}
}

第二种 立即执行

触发事件后函数立即执行并禁用函数不允许再次调用,在周期结束后解除禁用允许下一次调用函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function debounce(fn,delay) {
let timer;
return function () {
let that = this;
let args = arguments;
if (timer) clearTimeout(timer);
let allow = !timer; // 这里做一个限制
timer = setTimeout(() => {
timer = null;
}, delay)
if (allow) fn.apply(that, args) // 这里做一个限制
}
}

第三种 合体版

以上方法也可以加入一个参数,用于控制是否立即执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function debounce(fn,delay,immediate) {
let timer;
return function () {
let that = this;
let args = arguments;
if (timer) clearTimeout(timer);
if (immediate) { // 这里设置是否启用立即执行
let able = !timer;
timer = setTimeout(() => {
timer = null;
}, delay)
if (able) fn.apply(that, args)
} else {
timer = setTimeout(()=>{
fn.apply(that, args)
}, delay);
}
}
}

throttle

throttle: 节流阀,节流阀,节流。
其主要思想是:使连续触发的事件在一个周期内只执行一次,在周期内如果有新事件触发不予执行,周期结束后有事件则触发一次并开始新的周期。实际上就是稀释了执行频率。
主要用于处理事件高频触发的情况,使动作定期执行。

第一种 使用定时器

与debounce第一种的区别是,debounce需要清空并重新生成定时器。throttle是等待定时器执行完,然后在生成定时器。

1
2
3
4
5
6
7
8
9
10
11
12
13
function throttle(fn, delay) {
let timer;
return function() {
let that = this;
let args = arguments;
if (!timer) {
timer = setTimeout(() => {
timer = null;
fn.apply(that, args)
}, delay)
}
}
}

第二种 立即执行 + 开关

与debounce第一种的区别是,debounce需要清空并重新生成定时器。throttle是等待定时器执行完,然后在生成定时器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function throttle(fn, delay, immediate) {
let timer;
return function() {
let that = this;
let args = arguments;
if (!timer) {
if(immediate){
fn.apply(that, args)
timer = setTimeout(() => {
timer = null;
}, delay)
}else {
timer = setTimeout(() => {
timer = null;
fn.apply(that, args)
}, delay)
}
}
}
}

第三种 使用时间戳

使用时间戳可以避免生成定时器,利用时间来判定事件是否可以执行。

1
2
3
4
5
6
7
8
9
10
11
12
function throttle(fn, delay) {
let prev = 0;
return function() {
let now = Date.now();
let that = this;
let args = arguments;
if (now - prev > delay) { // 对比时间差
fn.apply(that, args);
prev = now;
}
}
}

还有很多种方法,这里只是简单的介绍一下思路。具体可以参考lodash的_.debounce_.throttle 等。

使用方法如下:

1
2
3
4
5
6
7
8
function handleClick(){
console.log('click')
}
function handleMove(){
console.log('move')
}
window.onclick = debounce(handleClick,800)
window.onmousemove = throttle(handleMove,800)

0%