我们在上篇中已经讲完了防抖,我们知道,防抖就是让频发事件,在多次触发中只执行一次,可以自由选择开始触发时机。那么节流又是什么呢?与防抖又有什么不同?那么本篇开始,我们将学习节流部分知识以及防抖与节流的应用场景。
节流(Throttle)
节流:就其字面理解,节省流量。频繁触发的事件当中,在规定间隔时间内,只触发一次,后续触发需要等待剩余时间结束。
为了便于理解,我们仍旧通过之前的例子稍作修改。
小明读到高中,需要寄宿,每月才回一次,于是爸爸办了一张银行卡用来给小明当生活支出。爸爸在银行办卡时,办理了每月定期向银行卡转一笔钱的业务,每当转出一笔钱时,银行系统设置了一个计时器,每次转完后,就开始计算等待下次转账的时间,等时间到了,再次执行转账。这样,小明就每个月都能收到生活费了。而不需要每次需要钱都去转一笔。
以上小故事中,银行的计时系统相当于节流中的定时器,而每个月转入一笔钱,相当于我们需要等待的时间,因此需要记录转账的时间来计算还需要等待多久执行。时间到了,就执行下一次操作。
思路解析
初次进入时,定时器置为null。要知道等待执行时的时间,因此我们需要记录一下上一次执行的时间 previous
,第一次执行时设为0,还需要记录当前时间 now
,那么我们需要等待的剩余时间就是:设定的间隔 - (当前时间 - 上一次执行时间)
, 假定我们设定的时间为 wait
, 剩余时间就是:remaining = wait - (now - previous)
那么怎么知道时间到了呢?根据我们上面的公式,当剩余时间 remaining
小于等于0时,那么就是该执行的时间点。到点了,就执行事件,还需要将上一次执行时间切换为当前时间并清除旧的计时器,如果没到时间,即remaining > 0
, 就啥也不干,等着就行。
然而,如果只是这样,那么定时器还没有开始,因此我们还需要判断是否为第一次执行。判断是否为首次执行,在防抖中也提到过,在这里就是判断剩余时间大于0时,当前是否有定时器。
代码实现
根据我们上面厘清的思路,可以尝试写代码如下:
const Throttle = (cb, wait) => {
let timer = null, previous = 0;
return function () {
let now = Date.now(),
remaining = wait - (now - previous);
if (remaining <= 0) {
cb()
previous = Date.now()
timer = clearTimer(timer)
} else if (!timer) {
timer = setTimeout(() => {
cb()
previous = Date.now()
timer = clearTimer(timer)
}, remaining)
}
}
}
参数处理
我们需要的回调是一个函数,如果传入的不是函数,可以抛出异常。另外,wait
需要兼容字符串的传入,我们在内部做一下转换;当传入字符串并且无法转为数字,应该设置一个默认值。
const Throttle = (cb, wait) => {
if (typeof cb !== 'function') {
throw new TypeError(`${cb} is ${typeof cb},but function we need !`)
}
wait = +wait
if (isNaN(wait)) wait = 300
let timer = null, previous = 0;
return function () {...}
}
另外,传入的回调可能还包含了自己的参数,因此我们也需要进行处理,在某些方法中,会用到this
,因此,还需将this做绑定处理。
// ...
return function (...params) {
// ...
if (remaining <= 0) {
cb.call(this, ...params)
// ...
} else if (!timer) {
timer = setTimeout(() => {
cb.call(this, ...params)
// ...
}, remaining)
}
}
演示对比
到这里,一个防抖函数就已经实现完了。这里以窗口滚动条事件进行演示,来看一下实现前后的效果对比吧!
- 未做防抖处理
- 防抖处理效果
完整代码
throttle.html
<div id="con">Throttle</div>
* {
margin: 0;
padding: 0;
}
html,
body {
height: 500%;
background: -webkit-linear-gradient(top, orangered, purple);
}
#con {
width: 100%;
height: 60px;
text-align: center;
line-height: 60px;
color: white;
font-size: 32px;
font-weight: bold;
}
<script>
function func() {
console.log('ok');
}
window.onscroll = Throttle(func, 500);
</script>
throttle.js
const Throttle = (cb, wait) => {
if (typeof cb !== 'function') {
throw new TypeError(`${cb} is ${typeof cb},but function we need !`)
}
wait = +wait
if (isNaN(wait)) wait = 300
let timer = null, previous = 0;
return function (...params) {
let now = Date.now(),
remaining = wait - (now - previous);
if (remaining <= 0) {
cb.call(this, ...params)
previous = Date.now()
timer = clearTimer(timer)
} else if (!timer) {
timer = setTimeout(() => {
cb.call(this, ...params)
previous = Date.now()
timer = clearTimer(timer)
}, remaining)
}
}
}
防抖与节流的区别
到此,防抖和节流的知识我们已经了解完了,我们发现,防抖和节流都是在固定时间内触发多次执行一次,那么防抖和节流之间有什么联系和区别呢?
防抖
- 防抖是在一个连续的相同行为中,只执行一次,若重复触发,则重新计时
- 防抖是将多次执行变为一次执行,只执行一次
- 作用是防止多次触发引起的非必要性能消耗
- 防抖是通过重设定时器来实现
节流
- 节流是在一个连续的相同行为中,每隔一段时间执行一次,重复执行时,需要等待剩余时间完成
- 节流是将多次执行变为每隔一段时间执行一次,可以执行多次
- 作用是降低事件触发的频次以达到优化性能,节流会稀释函数的执行频率
- 节流是通过计算剩余时间来实现
防抖与节流的应用场景
防抖
防抖适合处理一些无法预知的用户主动行为,具有非规律性,注重结果而非事件执行过程。
- 点击按钮事件
- 表单提交
- 用户输入信息进行联想提示
- keyup、keydown
节流
节流适合处理一些非用户主动行为或者可预知的用户主动行为,注重过程需要在视觉上体现。
- 监听滚动条滚动事件
- mouseover、mousedown
- 窗口resize事件
- 下拉加载更多(无限滚动)
防抖和节流都是为了优化高频次事件触发的非必要性能消耗。具体使用场景并非一成不变,需要根据实际项目需求来进行性选择。