# 节流与防抖
# 滚动监听例子
常见功能:返回顶部按钮
- 这个按钮只会在滚动到距离顶部一定位置之后才出现
# 简单实现
window.onscroll = () => {
let top = document.body.scrollTop || document.documentElement.scrollTop
console.log(top);
}
1
2
3
4
2
3
4
运行的时候会发现存在一个问题,这个函数的默认执行频率,太高了,只按一次键盘上的↓
就执行了9次
实际是不需要这么高的执行频率的,会消耗浏览器的性能
# 防抖
定义
对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限内,事件处理函数只执行一次。
基于上述场景,一种优化的思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,如果:
- 200ms内没有再次触发事件,那么就执行函数
- 200ms内触发了就重新计时
使用setTimeout实现的思路
let timer = null
window.onscroll = () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
let top = document.body.scrollTop || document.documentElement.scrollTop
console.log(top);
}, 200)
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
加入闭包避免污染全局作用域
function debounce(fn, delay) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn, delay)
}
}
window.onscroll = debounce(() => {
let top = document.body.scrollTop || document.documentElement.scrollTop
console.log(top);
}, 200)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 节流
定义
如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效
- 如果需要不断拖动滚动条,也能在某个时间间隔之后给出反馈
- 使用标志位与setTimeout
function throttle(fn, delay) {
let flag = true
return function () {
if (!flag) {
return false
}
flag = false
setTimeout(() => {
fn()
flag = true
}, delay)
}
}
window.onscroll = throttle(() => {
let top = document.body.scrollTop || document.documentElement.scrollTop
console.log(top);
}, 200)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 使用时间戳
function throttle(fn, delay) {
let start = Date.now()
return function () {
if (start + delay >= Date.now()) {
return
}
start = Date.now()
fn()
}
}
window.onscroll = throttle(() => {
let top = document.body.scrollTop || document.documentElement.scrollTop
console.log(top);
}, 200)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 业务场景
- 搜索框input事件,实时搜索可以使用节流方案
- 需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染一般使用防抖
# 带参数
# 防抖
function debounce(fn, delay) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
fn = fn.bind(this, ...arguments)
timer = setTimeout(fn, delay)
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 节流
function throttle(fn,delay){
let start = Date.now()
return function(){
if(start+delay>=Date.now()){
return
}
start = Date.now()
fn.apply(this,[...arguments])
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 加强版节流
throttle 与 debounce “合体”思路,目的是解决
如果用户的操作十分频繁——他每次都不等 debounce 设置的 delay 时间结束就进行下一次操作,于是每次 debounce 都为该用户重新生成定时器,回调函数被延迟了不计其数次。频繁的延迟会导致用户迟迟得不到响应,用户同样会产生“这个页面卡死了”的观感
function throttle(fn, delay) {
let start = 0, timer = null
return function () {
let end = Date.now()
let context = this;
let args = arguments
if (end - start < delay) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
start = end
fn.apply(context, [...args])
}, delay)
} else {
start = end
fn.call(context, ...args)
clearTimeout(timer)
}
}
}
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