# 理解JS函数节流和函数防抖
# 函数防抖和函数节流对比
它们的目标都是为了防止过多的无意义函数调用。
函数防抖的目的是在多次连续触发事件时,在指定时间内(delay
参数)若再次触发调用回调函数,那么将忽略当前的函数调用,只有在经过指定时间内,没有再次触发事件时,才会真正的调用回调函数。
函数节流的目的是为了限制回调函数的最高执行频率的(比如 1s 内最多执行 2 次)。在单位时间内(wait
参数)最多执行一次回调函数调用。多余的回调函数调用将被忽略。
二者与普通回调函数调用的可视化对比如下:
# throttle 应用场景
函数节流有哪些应用场景?哪些时候我们需要间隔一定时间触发回调来控制函数调用频率?
DOM
元素的拖拽功能实现(mousemove
)- 射击游戏的
mousedown/keydown
事件(单位时间只能发射一颗子弹) - 计算鼠标移动的距离(
mousemove
) - Canvas 模拟画板功能(
mousemove
) - 搜索联想(
keyup
) - 监听滚动事件判断是否到页面底部自动加载更多:给
scroll
加了debounce
后,只有用户停止滚动后,才会判断是否到了页面底部;如果是throttle
的话,只要页面滚动就会间隔一段时间判断一次
/**
* 用于限制 fn 函数在 period 时间段内只调用一次,即限制 fn 调用的频率
* 示例中实现了首次和末次一定会被调用,中间调用被限定为一定频率
*
* @param {Function} fn 要被节流的函数
* @param {number} [period=200] 被节流的时间段
* @returns 一个匿名函数包装
*/
function throttle (fn, period=200) {
let _lastTime = null
let _timer = null
return function (...args) {
const _nowTime = +new Date()
_timer && clearTimeout(_timer)
if (!_lastTime || _nowTime - _lastTime > period) {
fn.apply(this, args)
_lastTime = _nowTime
} else {
// 确保最后一次即使不满足 period 时间段,但仍会调用
// 使用箭头函数来确保 this 不变
_timer = setTimeout(() => {
fn.apply(this, args)
}, period)
}
}
}
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
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
# debounce 应用场景
函数去抖有哪些应用场景?哪些时候对于连续的事件响应我们只需要执行一次回调?
- 每次
resize/scroll
触发统计事件 - 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)
/**
* @param {Function} fn 要实现函数防抖的原函数
* @param {Number} delay 延迟时间
* @param {Boolean} now 是否第一次立即调用后启用防抖
* @return {Function} 添加防抖功能的包装函数
*/
// 最后一次调用总是会被执行
function debounce (fn, wait = 800, now = true) {
let __timer = null
let isFirst = now
return function (...args) {
if (__timer) clearTimeout(__timer)
if (isFirst) {
fn.apply(this, args)
isFirst = false
} else {
__timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22