函数去抖(debounce)与 函数节流(throttle)

前言

以下场景往往由于事件频繁被触发,因而频繁执行 DOM 操作、资源加载等重行为,导致 UI 停顿甚至浏览器崩溃。

  • window 对象的 resizescroll 事件
  • 拖拽时的 mousemove 事件
  • 射击游戏中的 mousedownkeydown 事件
  • 文字输入、自动完成的 keyup 事件

实际上对于 windowresize 事件,实际需求大多为停止改变大小 n 毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了 debouncethrottle 两种解决办法。

什么是 debounce(去抖)

debounce 定义

如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。

也就是说当调用动作 n 毫秒后,才会执行该动作,若在这 n 毫秒内又调用此动作则将重新计算执行时间。

1
2
3
4
5
6
7
/**
* 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 idle,action 才会执行
* @param idle {number} 空闲时间,单位毫秒
* @param action {function} 请求关联函数,实际应用需要调用的函数
* @return {function} 返回客户调用函数
*/
debounce(idle, action);

debounce 简单实现

1
2
3
4
5
6
7
8
9
10
11
var debounce = function (idle, action) {
var last;
return function () {
var ctx = this,
args = arguments;
clearTimeout(last);
last = setTimeout(function () {
action.apply(ctx, args);
}, idle);
};
};

如果需要立即执行一次

1
2
3
4
5
6
7
8
9
10
11
12
function debounce(event, time, flag) {
let timer = null;
return function (...args) {
clearTimeout(timer);
if (flag && !timer) {
event.apply(this, args);
}
timer = setTimeout(() => {
event.apply(this, args);
}, time);
};
}

什么是 throttle(节流)

throttle 定义

如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。

也就是会说预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。

1
2
3
4
5
6
7
/**
* 频率控制 返回函数连续调用时,action 执行频率限定为 次 / delay
* @param delay {number} 延迟时间,单位毫秒
* @param action {function} 请求关联函数,实际应用需要调用的函数
* @return {function} 返回客户调用函数
*/
throttle(delay, action);

throttle 简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//时间戳实现:第一次事件肯定触发,最后一次不会触发
var throttle = function (delay, action) {
var last = 0;
return function (...args) {
if (Date.now() - last > delay) {
last = Date.now();
action.apply(this, args);
}
};
};
//定时器实现:第一次事件不会触发,最后一次一定触发
var throttle = function (event, time) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
event.apply(this, args);
}, time);
}
};
};

underscore 里有该实现,可以多多参考一下。