节流和防抖

为什么我们需要节流和防抖?

JavaScript遵循事件驱动的编程模型,用户的某些特定的行为会触发响应的回调函数,行为发生的频率是由用户控制的,频繁的触发回调函数可能会产生性能问题,这个时候我们可以从控制如何响应回调函数入手去优化性能。
考虑如下真实的场景:
一个图片墙功能,需要我们延迟加载图片,这就需要我们去监听页面滚动事件,在回调函数中我们判断未加载的图片当前是否展示在了浏览器可视区域,如果展示了,就去加载页面的真实地址。如果不对响应函数进行处理的话,scroll事件会频繁的计算图片的位置和当前滚动的位置,产生页面卡顿的现象。
一个input搜索功能,当用户从键盘输入内容的时候,向后端接口请求所有匹配的内容,类似于百度搜索栏一样的功能,如果我们不对响应函数加以控制,会出现什么现象呢?我们每输入一个字符,就都会向后端发出一次请求,但其实我们一次完整的输入过程并没有完成,这些中间状态是不必要的,请求接口也造成了资源的浪费。
处理以上情况通常的做法是使用节流和防抖。

节流

拿自来水管中的水流为例,节流的意思是降低水流的大小,用在这个地方是降低了响应响应时间的频率,限制了两次响应的时间间隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var start = Date.now()
var timeId = null;
var dur = 1000
function log() {
console.log('log')
}
function throttle() {
console.log('emit')
return function() {
var now = Date.now()
clearTimeout(timeId)
if (now - start >= dur) {
log()
start = now
} else {
timeId = setTimeout(log, dur)
}
}
}
window.onscroll = throttle()

这样的话我们就实现了节流的效果 这里我们每隔一秒钟响应一次 如果两次触发回调的时间间隔小于一秒我们就可以设置一个一秒钟的定时器这样就可以保证不会错误的丢掉响应 具体的时间间隔可以适当进行调整 这里只是做一个演示。

上面的代码实现了我们想要的功能但是从可用性角度来看是无法接受的,throttle函数依赖了全局的start和timeId变量并且与响应函数也存在强耦合的关系,下面我们稍微修改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function throttle(fn, dur) {
var start = Date.now()
var timeId = null;
return function() {
var now = Date.now()
clearTimeout(timeId)
if (now - start >= dur) {
fn()
start = now
} else {
timeId = setTimeout(fn, dur)
}
}
}
function log() {
console.log('log')
}
window.onscroll = throttle(log, 1000)

防抖

防抖这个术语在电学中是将多个电信号合并成一个电信号
在这里我们接收到一个响应信号时不是立即执行处理函数而是等待一定时间,如果这段时间内没有接收到信号则执行处理函数,如果再次接收到了那么重置开始时间重新等待,这样就可以做到在一定时间间隔内将多个信号合并

1
2
3
4
5
6
7
8
9
10
11
function log() {
console.log('log')
}
function debounce(fn, dur) {
var timeId = null
return function() {
clearTimeout(timeId)
timeId = setTimeout(fn, dur)
}
}
window.onscroll = debounce(log, 1000)

有些时候我们需要先执行一次处理函数然后再进行等待

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function log() {
console.log('log')
}
function immediate(fn, dur) {
var timeId = null
var timeout = true
return function() {
if(timeout) {
fn()
timeout = false
} else {
clearTimeout(timeId)
timeId = setTimeout(() => {
fn()
timeout = true
}, dur)
}
}
}
window.onscroll = immediate(log, 1000)

总结

节流和防抖的概念在性能优化中是处理浏览器回调函数的常见手段,对提升我们页面性能和用户体验来说非常重要,还可以减少中间状态时不必要的请求,是非常重要的概念。