0%

JS防抖和节流

1. 防抖 (debounce)

  • 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
  • 当事件密集触发时,函数的触发会被频繁的推迟;
  • 只有等待了一段时间也没有事件触发,才会真正的执行响应函数;

应用场景

例如: 输入框中频繁的输入内容 频繁的点击按钮,触发某个事件 监听浏览器滚动事件,完成某些特定操作 用户缩放浏览器的resize事件等等

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
// debounce.js
function debounce(fn, delay, immediate = false) {
// 1.定义一个定时器, 保存上一次的定时器
let timer = null
let isInvoke = false

// 2.真正执行的函数
const _debounce = function(...args) {
return new Promise((resolve, reject) => {
// 取消上一次的定时器
if (timer) clearTimeout(timer)

// 判断是否需要立即执行
if (immediate && !isInvoke) {
const result = fn.apply(this, args)
resolve(result)
isInvoke = true
} else {
// 延迟执行
timer = setTimeout(() => {
// 外部传入的真正要执行的函数, 拿到函数返回值并调用resolve
const result = fn.apply(this, args)
resolve(result)
isInvoke = false
timer = null
}, delay)
}
})
}

// 封装取消功能
_debounce.cancel = function() {
if (timer) clearTimeout(timer)
timer = null
isInvoke = false
}

return _debounce
}

使用

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
<input type="text">
<button id="cancel">取消</button>

<script src="debounce.js"></script>
<script>
const inputEl = document.querySelector("input")
let counter = 0

const inputChange = function(event) {
console.log(`触发第${++counter}次`, this, event)
// 返回值
return "aaaaaaaaaaaa"
}

// 防抖处理 将原本函数放入 debounce 作为参数, 之后直接使用 debounceChange 即可
const debounceChange = debounce(inputChange, 3000, false)
//文本框事件触发
inputEl.oninput = (...args) => {
// 这种调用方法需要重新绑定this, 所写的debounce函数是没有问题的, 在实际开发中使用相对这里会简单一点
debounceChange.apply(inputEl, args).then(res => {
console.log("Promise的返回值结果:", res)
})
}

// 取消功能
const cancelBtn = document.querySelector("#cancel")
cancelBtn.onclick = function() {
debounceChange.cancel()
}
</script>
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
// react
import { Input, Space } from 'antd';
import { debounce } from '@/util';
import { useEffect, useState } from 'react';

export default () => {
const [text, setText] = useState('');

useEffect(() => {
const delayed = debounce(() => {
console.log(text);
}, 1000);
delayed();
return delayed.cancel;
}, [text]);

return (
<Space direction="vertical">
<Input
value={text}
onChange={(e) => {
setText(e.target.value);
}}
/>
</Space>
);
};

节流 (throttle)

  • 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数;
  • 不管在这个中间有多少次触发这个事件,执行函数的频率总是固定的;

应用场景

例如: 鼠标移动事件 王者荣耀攻击键, 点击再快也是以一定攻速(频率)进行攻击等等

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
function throttle(fn, interval, options = { leading: true, trailing: false }) {
// 1.记录上一次的开始时间
const { leading, trailing, resultCallback } = options
let lastTime = 0
let timer = null

// 2.事件触发时, 真正执行的函数
const _throttle = function(...args) {
return new Promise((resolve, reject) => {
// 2.1.获取当前事件触发时的时间
const nowTime = new Date().getTime()
if (!lastTime && !leading) lastTime = nowTime

// 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}

// 2.3.真正触发函数
const result = fn.apply(this, args)
resolve(result)
// 2.4.保留上次触发的时间
lastTime = nowTime
return
}

if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
lastTime = !leading ? 0: new Date().getTime()
const result = fn.apply(this, args)
resolve(result)
}, remainTime)
}
})
}

_throttle.cancel = function() {
if(timer) clearTimeout(timer)
timer = null
lastTime = 0
}

return _throttle
}

使用

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
<input type="text">
<button id="cancel">取消</button>

<script src="throttle.js"></script>
<script>
const inputEl = document.querySelector("input")
let counter = 0
const inputChange = function(event) {
console.log(`触发第${++counter}次`, this, event)
return 11111111111
}

// 节流处理
const _throttle = throttle(inputChange, 3000, {
leading: false,
trailing: true,
})
inputEl.oninput = (...args) => {
_throttle.apply(inputEl, args).then(res => {
console.log("Promise的返回值结果:", res)
})
}

// 取消功能
const cancelBtn = document.querySelector("#cancel")
cancelBtn.onclick = function() {
_throttle.cancel()
}
</script>