# 防抖和节流
# 前言
# 一、概念如何理解?
# 防抖
概念:触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间。 可以理解为游戏中法师的施法前摇,1s的前摇如果没有被打断,则施法成功;如果1s内被打断,则重新开始计时。
# 节流
概念:高频时间触发,但n秒内只会执行一次,所以节流会稀释函数的执行频率。 可以理解为FPS游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹。
# 相同点
- 目的都是,降低回调执行频率。节省计算资源。
# 不同点
- 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout 和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。
- 函数防抖关注一定时间连续触发的事件只在最后执行一次,而函数节流侧重于一段时间内只执行一次。
# 函数防抖的应用场景
连续的事件,只需触发一次回调的场景有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
# 函数节流的应用场景
间隔一段时间执行一次回调的场景有:
- 滚动加载,加载更多或滚到底部监听
- 谷歌搜索框,搜索联想功能
- 高频点击提交,表单重复提交
# 二、如何实现?
# 1、基础版
快速请求,连续发送了多个相同的api,这样容易对服务器造成较大压力
// 模拟api请求
function ajax(api) {
console.log('request: ', api)
}
// 模拟快速提交
for (let i = 1; i <= 10; i++) {
ajax('/user');
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
使用节流解决:
// 节流
// 效果:一定时间间隔内,只调用一次fn
function throttle(fn, wait) {
let lastTime = null
return (args) => {
if (!lastTime || Date.now() - lastTime >= wait) {
fn(args)
lastTime = Date.now()
}
}
}
let throttleAjax = throttle(ajax, 1000);
// 测试
for (let i = 1; i <= 10; i++) {
setTimeout(() => {
console.log(i)
throttleAjax('/user')
}, 500 * i)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
使用防抖解决:
// 防抖
function debounce(fn, wait) {
let timer = null
return (args) => {
clearTimeout(timer)
timer = setTimeout(() => {
fn(args)
}, wait)
}
}
let debounceAjax = debounce(ajax,1000)
// 测试
for (let i = 1; i <= 10; i++) {
setTimeout(() => {
console.log(i)
debounceAjax('/user')
}, 500 * i)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2、升级版
支持 leading 和 trailing 配置
- leading:第一次触发时执行
- trailing:触发完毕后执行一次
function ajax(api) {
console.log('request: ', api);
}
function debounce(fn, delay, options = {}) {
let leading = options.leading !== undefined ? !!options.leading : false; // 首次触发后立即执行,默认为false
let timer = null;
let firstInvoked = true;
return function() {
let context = this;
let args = arguments;
if (firstInvoked) {
firstInvoked = false;
if (leading) {
fn.apply(context, args);
return;
}
}
timer && clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
}
}
function throttle(fn, delay, options = {}) {
let leading = options.leading !== undefined ? !!options.leading : true; // 首次触发后立即执行,默认为true
let trailing = options.trailing !== undefined ? !!options.trailing : true; // 触发结束后是否执行一次,默认为true
let lastTime = null;
let timer = null;
let firstInvoked = true;
return function() {
let context = this;
let args = arguments;
let remaining = delay - (Date.now() - lastTime);
function invokeFn(isTrailing) {
fn.apply(context, args);
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = Date.now();
}
if (firstInvoked) {
leading && invokeFn();
firstInvoked = false;
return;
}
if (!lastTime || remaining <= 0) {
invokeFn();
} else if (trailing && !timer) {
timer = setTimeout(() => {
invokeFn()
}, delay);
}
};
}
let throttleAjax = throttle(ajax, 3000, {leading: true, trailing: true });
let debounceAjax = debounce(ajax, 3000, {leading: false});
// 测试
for (let i = 1; i <= 10; i++) {
setTimeout(() => {
console.log(i);
debounceAjax("/user");
// throttleAjax("/user");
}, 1000 * i);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76