源码 {#源码}
前往Codepen在线体验。
介绍 {#介绍}
防抖 和节流是两个JS中的概念,它们被广泛应用于被频繁触发的事件中,如搜索框在输入时会弹出候选列表:如果每次输入都发送一个AJAX请求来获取数据,那么后台就要被刷爆了。所以,这里就引出了本文要介绍的概念。
防抖 {#防抖}
在 x
秒内 ,无论调用多少次这个函数,它只会在最后一次调用 的 x
秒后被真正执行。
在参考文章里举了这样一个例子:
一个小孩向妈妈要蛋糕,他的妈妈被弄烦了。所以她告诉小孩说,只有他能保持安静一个小时,才能得到蛋糕。中途任意要一次就重新开始计算。
我只是照着原文,摘了重点部分翻译过来放在上面。
节流 {#节流}
在x
秒内 ,无论调用多少次这个函数,它只会被执行一次。
在参考文章里举了这样一个例子:
还是那个小孩要蛋糕,但这次他的妈妈允许他无限制地要。尽管如此,不论他要多少次,只能在一小时内获取到一块蛋糕。如果他不再要了,自然也就不给了。
也是照着原文,摘了重点部分翻译过来放在上面。
实现 {#实现}
虽然这个概念是比较有用的,但是原生JS并没有给我们提供一个接口。无妨,借助setTimeout
可以轻松实现。
防抖 {#防抖-2}
我们使用了JS里强大的闭包:
|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11
| function debounce(fn, delay) { let timeout = undefined return function() { clearTimeout(timeout) timeout = setTimeout(() => { timeout = undefined fn.apply(this, arguments) }, delay) } }
|
可以看出,这个返回的函数无论被调用多少次,第一件事就是清除掉原有的定时器。如果到时间了,就执行函数fn
;如果被清除掉了,那就不执行。
节流 {#节流-2}
节流可以使用setTimeout
实现,也可以借助Date
判断调用时间。
使用setTimeout {#使用setTimeout}
贴代码:
|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13
| function throttle(fn, delay) { let timeout = undefined return function() { if (timeout !== undefined) { return } timeout = setTimeout(() => { timeout = undefined fn.apply(this, arguments) }, delay) } }
|
当定时器存在时,调用返回的函数不会做任何事。直到定时器被执行,timeout
被重新设置成了undefined
,才能进行下一轮操作。
使用Date {#使用Date}
贴上代码:
|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function throttleDate(fn, delay) { let prev = Date.now() return function() { const now = Date.now() if (now - prev < delay) { return } prev = now fn.apply(this, arguments) } }
|
如果当前时间now
和上一次的时间prev
的差比预期时间delay
小,就什么也不做。否则,就执行函数fn
,并且重置上一次的时间prev
。
测试 {#测试}
写了这么多逻辑,没有测试自然是不合适的。这里就写个简单的网页来测试了:
|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5
| <button id="b1">Debounce: <span>0</span></button> <br><br> <button id="b2">Throttle: <span>0</span></button> <br><br> <button id="b3">Throttle(Date): <span>0</span></button>
|
加上点简单粗暴的样式:
|-----------------------|-----------------------------------------------------------|
| 1 2 3 4 5 6 7
| * { font-size: 1.2rem; } button { width: 200px; }
|
当然,还要把这些按钮的事件都绑定上:
|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9
| function bind(id, wrapper) { document.getElementById(id).onclick = wrapper(function() { this.querySelector('span').innerText++ }, 500) } bind('b1', debounce) bind('b2', throttle) bind('b3', throttleDate)
|
这里的bind
函数只是做了把指定id
的元素绑定上一个回调函数,执行时会使得它子节点中的span
元素自增。
同时,这个回调函数会被我们传入的wrapper
包装起来,也就是debounce
、throttle
或throttleDate
,并且延时都是500ms
,也就是0.5s
。
可以点击右侧的目录回到文章开头给源码的地方,到Codepen里实时预览最终效果。
应用 {#应用}
- 可以给按钮的
onclick
事件进行节流 ,用于防止用户频繁点击按钮。 - 可以给窗口的
resize
事件进行防抖 ,当最终重新调整大小后,再重新渲染页面。 - 可以给输入框的
keyup
,keydown
等事件进行防抖 ,当用户停止输入一段时间后弹出提示。 - 其实输入框的事件进行节流 也可以,用于实时但不要太频繁地根据用户的输入弹出提示。
- 当NodeJS 需要频繁更新文件到硬盘里的时候,进行防抖 处理,这样只有在操作停止的一段时间后 才会更新到硬盘里,有效减少IO操作。