防抖(debounce)和节流(throttle)
# 概念部分
# 防抖(debounce)
触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
- 输入框的
搜索联想
,用户在不断输入值时,用防抖来节约请求资源 - window 触发 resize 的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
- 如果输入框的搜索联想不添加防抖函数,用户没输入一个字符都去请求一次接口,然后重新赋值给联想输入的列表,如此反复就真的会出现
抖动
的情况,所以防抖的名称不是白叫的。感兴趣可以自行体验下~
# 节流(throttle)
就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 就好像水龙头滴水,每间隔一段时间就流下一滴
# 防抖和节流的区别:
假设一个场景:有一个按钮,用户在
3s 内
,以0.1s 每次的速度点击
。点击按钮后会触发一个函数。函数分别用防抖和节流来处理,查看区别:
防抖:因为以
0.5S/次
的速度点击,如果防抖控制的是每次触发时间不得小于 0.5s。那按钮最后触发的函数次数只有 1 次
。因为前面几次点击都被重置,然后重新计算了函数执行时间。节流:节流控制的也是
0.5s 内只执行一次
。那么 3s 后,函数会被执行 7 次
,最后一次是因为函数没再次被销毁。
demo 的实现
先通过引入 underscore
来使用防抖和节流
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/underscore.js/1.10.2/underscore-min.min.js"></script>
</head>
<body>
<button id="btn">点击我</button>
<script>
let btn = document.getElementById('btn')
var debounceFn = _.debounce(function() {
console.log('触发防抖', new Date())
}, 500)
var throttleFn = _.throttle(function() {
console.log('触发节流', new Date())
}, 500)
btn.addEventListener('click', debounceFn)
btn.addEventListener('click', throttleFn)
let allTime = 0
let clickTime = setInterval(function() {
if (allTime > 3000) {
clearInterval(clickTime)
} else {
btn.click()
allTime += 100
}
}, 100)
</script>
</body>
</html>
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
运行结果:
防抖函数分为非立即执行版和立即执行版。
非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
# 通过学习 underscore 自己实现防抖和节流
用到的知识点:
-
1.1 防抖和节流都用到了闭包,在函数内在返回一个函数,防抖中有
timeout
。节流中有previous
、timeout
。使用闭包可以确保他们多次点击触发函数的时候访问的是同一个timeout
-
2.1 apply 用于改变 this 的指向,为了兼容 es5,所以 setTimeOut 中并没有使用箭头函数,如果不用 apply 改变 this 指向,那 this 就会指向 setTimeOut 中
2.2 函数中可能接收多个参数,参数个数是不固定的,这时候我们可以用
arguments
获取参数数组,而apply
第二个参数刚好可以接收一个数组
防抖实现
/**
* @desc 函数防抖
* @param func 函数
* @param wait 延迟执行毫秒数
* @param immediate true 表立即执行,false 表非立即执行
*/
function debounce(func, wait, immediate) {
let timeout
return function() {
let context = this
let args = arguments
if (timeout) clearTimeout(timeout)
if (immediate) {
var callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) func.apply(context, args)
} else {
timeout = setTimeout(function() {
func.apply(context, args)
}, wait)
}
}
}
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
节流实现
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 表时间戳版,2 表定时器版
*/
function throttle(func, wait, type) {
let previous = 0
let timeout
return function() {
let context = this
let args = arguments
if (type === 1) {
let now = Date.now()
if (now - previous > wait) {
func.apply(context, args)
previous = now
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}
}
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