防抖与节流(下篇)


我们在上篇中已经讲完了防抖,我们知道,防抖就是让频发事件,在多次触发中只执行一次,可以自由选择开始触发时机。那么节流又是什么呢?与防抖又有什么不同?那么本篇开始,我们将学习节流部分知识以及防抖与节流的应用场景。

节流(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事件
  • 下拉加载更多(无限滚动)

防抖和节流都是为了优化高频次事件触发的非必要性能消耗。具体使用场景并非一成不变,需要根据实际项目需求来进行性选择。


文章作者: 塵影
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 塵影学习笔记 !
评论
  目录