防抖和节流都是对多次频繁触发的处理,但是二者又有很大不同。
防抖是将多次触发情况变为一次触发,如下图蓝色的圆,准备从左边走到右边,而每次点击按钮都会让其重新回到左边。我们把左到右这段距离比作防抖限制的时间wait,那么每次触发,只要wait时间没有走完,则wait会重新开始计时。
节流则是将多次触发变为每隔一段时间触发一次。如下图红色的球,每次触发,只要还没走完wait时间,就不会从头开始走。
对于防抖函数而言,我们要设置一个timer,作为每次触发时的标杆。
如下面的流程图所示,当timer存在,说明上一次的wait时间没有走完,需要将timer清空,重新计时。如果timer不存在,就赋值一个timer开始计时,再判断函数是否要立即执行,是的话则执行代码,清空timer,不是的话即开始等待wait时间的结束。如果wait时间还未结束,函数又被触发了,则回到顶部的函数调用,再进行一轮判断。
节流函数则在函数调用时判断是否走完了wait,是则执行,不是则继续等待。
本篇小文主要是帮自己梳理防抖和节流的不同,以及其中的实现思路。学习过程里一直参看 JS | 前端进阶之道 。为了方便查阅,特将其中的防抖函数实现代码贴在下方。
// 这个是用来获取当前时间戳的
function now() {
return +new Date()
}
/**
* 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,是否立即调用函数
* @return {function} 返回客户调用函数
*/
function debounce (func, wait = 50, immediate = true) {
let timer, context, args
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空缓存的定时器序号
timer = null
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {
func.apply(context, args)
context = args = null
}
}, wait)
// 这里返回的函数是每次实际调用的函数
return function(...params) {
// 如果没有创建延迟执行函数(later),就创建一个
if (!timer) {
timer = later()
// 如果是立即执行,调用函数
// 否则缓存参数和调用上下文
if (immediate) {
func.apply(this, params)
} else {
context = this
args = params
}
// 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
// 这样做延迟函数会重新计时
} else {
clearTimeout(timer)
timer = later()
}
}
}
节流函数代码也是借鉴 JS | 前端进阶之道,不过只取了部分以实现功能。这里对wait是否走完的判断依据为当前时间和上一次调用时间的差值:
function throttle (func, wait) {
var context, args
// 设置前一个函数被触发的时间戳
var previous = 0
// 返回给用户调用的回调
return function () {
var now = new Date().getTime()
// 首次进入
if (!previous) previous = now
// 准备context和args
context = this
args = arguments
// 计算剩余的时间时长
var remaining = wait - (now - previous)
// 如果 now 超过了previous + wait,可以执行函数
if (remaining <= 0) {
previous = now
timeout = null
result = func.apply(context, args)
context = args = null}
}
}
之前学习节流时,做过一个小练习,也贴在此,若有兴趣,可以看看效果,持续点击+按钮即可。