在DOM事件监听时,常见的mousemove、scroll、keydown、resize操作都可能导致在短时间内事件过于频繁的触发。如果在此时进行了DOM操作,极大可能导致浏览器运行崩溃。为了防止此类事件的发生,我们引入节流(throttle)与防抖(debounce)这一对概念

定义与应用场景

节流(throttle):减少过快的调用让一个函数不要执行的太频繁

防抖(debounce):对于一定时间段连续的函数调用,只让其执行一次

节流应用场景

  • DOM 元素的拖拽功能实现(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 计算鼠标移动的距离(mousemove)
  • 搜索联想(keyup)
  • 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次

防抖应用场景

  • 每次 resize/scroll 触发统计事件
  • 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)

防抖的实现

防抖的基本思路是,当触发事件,判断当前是否存在定时器。如果没有,则创建定时器。如果存在,则清楚之前的定时器,重新创建定时器。这样做保证了在固定间隔内多次触发事件,只会执行一次。

例子为模拟Ajax表单提交,规定点击间隔不能超过500ms

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
<body>
<p id="num">点击0次</p>
<button>click</button>
<script>
function send() {
let total = 0
let changeNum = () => {
total++
document.querySelector('#num').textContent = `点击${total}次`
}
return changeNum
}

function debounce(fn, wait) {
let timer
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
fn()
}, wait)
}
}

let butt = document.querySelector('button')
butt.addEventListener('click', debounce(send(), 500))
</script>
</body>

节流的实现

节流实现的基本思路是,规定一个时间间隔,第一次触发立即执行。之后每一次触发时,判断上一次触发时间,若时间差小于间隔,则该次触发无效。若本次触发时间差大于间隔,则触发事件,并设置新的触发时间。

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
<body>
<p id="num">点击0次</p>
<button>click</button>
<script>
function send() {
let total = 0
let changeNum = () => {
total++
document.querySelector('#num').textContent = `点击${total}次`
}
return changeNum
}

function throttle(fn, gap) {
let last = 0
return function () {
let curr = new Date()
if (curr-last > gap){
fn()
last = curr
}
}
}
let butt = document.querySelector('button')
butt.addEventListener('click', throttle(send(), 500))
</script>
</body>