防抖:高频触发的情况下只在触发的开始或结束执行
节流:高频触发的情况下按照设定频率执行(每隔一段时间执行一次)
更通俗的语言来描述:你做了一个输入框并监听
onchange
事件,随后在某个标签中显示当前输入框包含数字1的个数
有个用户闲来无事按着数字1,输入框内容唰唰唰更新,但是等他松手了才会去看统计结果,那之前每次都要统计有意义吗?
不错,他既然某段时间高频触发,那我们只要最后一次统计不就完事了?于是有了防抖
但是,产品设计说这样用户体验不好啊!要是用户一直按着数字1,这个统计结果他不更新,还以为我们产品坏了呢!
那我们每隔一段时间统计一次不就好了?于是有了节流
思考一下:防抖节流我们需要怎么选择?
按上面的故事讲到,那节流就是比防抖好咯?这是不准确的,高频触发的情况下节流和防抖都能减少无用的运算,提高性能,但究竟用哪个,需要视具体情况而使用。上面的场景节流比防抖好是因为要考虑到高频触发过程中需要展示结果给用户,如果不需要做相应展示,那就可以考虑使用防抖了。
# 基于定时器的实现
# 抖动
每次抖动结束的时候触发
function debounce(func, delay) {
let timer = null;
return function () {
const context = this;
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func.apply(context, arguments);
timer = null;
}, delay)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
可以看到上面完成了一个简单的防抖,但是出现了保存this
和arguments
不太好看
于是我们使用箭头函数和扩展运算符
function debounce(func, delay) {
let timer = null;
return (...args) => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
可能我只想开始的时候触发,所以写一个带参数的进阶版本
function debounce(func, delay, immediate = false) {
let timer = null;
return (...args) => {
if (timer) {
clearTimeout(timer);
}
if (immediate) {
if (!timer)
func.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, delay)
} else {
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay)
}
}
}
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
# 节流
结束时执行
function throttle(func, delay) {
let timer = null;
return (...args) => {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
开始时执行,又是一个带参数的新版本(滑稽
function throttle(func, delay, immediate) {
let timer = null;
return (...args) => {
if (!timer) {
if (immediate) {
func.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, delay);
} else {
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 基于时间戳的实现
# 抖动
当然,这种有关时间间隔的,自然也能使用时间戳写法
function debounce(func, delay) {
let prev = new Date().getTime();
return (...args) => {
const now = new Date().getTime();
if (Math.abs(now - prev) >= delay) {
func.apply(this, args);
}
prev = now;
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Math.abs()
使用原因见下
# 节流
function throttle(func, delay) {
let prev = new Date().getTime();
return (...args) => {
const now = new Date().getTime();
if (Math.abs(now - prev) >= delay) {
func.apply(this, args);
prev = now;
}
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
用时间戳的写法,相当于immediate = true
的版本
我这里会使用到
Math.abs()
的原因是:时间戳跟系统时间有关,now - prev
出现负数的情况下,该节流失效且不再触发func