小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
面试官:请介绍下防抖和节流--
我:防抖是单位时间内再次触发事件时,会重新开始计时,保证单位时间内事件只执行一次,节流是单位时间内不管触发多少次事件,只执行一次,两个都可以用来节约性能
面试官:代码撸一下?
我:没问题啊
前言
听到这个的时候心里是狂喜的,这个多简单,这个我会啊,难不倒我
第一版(这是当时我写的)
- 💥防抖
function debounce (func) {
let timer = null
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
func.call(this, arguments)
}, 1000)
}
}
创建一个标记用来存放定时器的返回值
再次触发事件时,把前一个定时器清除
根据定时器,每隔一段时间触发,反复触发又会重新计时
- 💥节流
function throttle (func) {
let currentState = true
return function () {
if (!currentState) {
return
}
currentState = false
setTimeout(() => {
func.call(this, arguments)
currentState = true
}, 1000)
}
}
通过保存一个标记,表示可否触发的状态
判断状态不为true 禁止运行
将 当前currentState 设置为 false,一段时间内不在触发
执行完事件后,再将这个标志设置为 true
🌰
难不倒我!
面试官扫了眼我的代码,看着我认真的说:老铁没毛病,还有补充嘛?
我:(?????一万个问号)没有了吧
内心是瞬间就紧张起来了,我觉得这里有坑,但是又不知道自己这个有什么问题,面试就这样结束了,回来后我查阅了防抖和节流的资料,总结了四个字:才疏学浅!
防抖和节流
在开发过程中,我们经常会遇到各种场景中需要触发一些事件,比方说click,change,input,又或者是mousemove,scroll等,可能这些事件会频发触发,又或者有些调皮的用户在不知情的情况下间接频繁触发,事件处理函数就会被不停的调用
而更多的是,我们的页面是通过ajax请求数据渲染的,如果频繁的建立连接,服务器需要重复的响应结果,数据量很大的情况下呢?这里有人会说,缓存不就可以了,👍,确实听上去没毛病。那我们可不可以在触发事件时就减小压力呢?
✍️防抖
上面第一版代码没有毛病,平时我也是这么写的,简单方便,但是后来回忆起来,当时面试官是希望我能写的更加完美吧,错过了,/(ㄒoㄒ)/~~
function debounce (func, wait, immediate) {
let timer
return function () {
const _this = this
const args = arguments
if (timer)clearTimeout(timer)
if (immediate) {
const callNow = !timer
timer = setTimeout(function () {
timer = null
}, wait)
if (callNow) func.call(_this, args)
} else {
timer = setTimeout(function () {
func.apply(_this, args)
}, wait)
}
}
}
我们来一步步分析
首先,参数,func:表示执行函数,wait:需要等待的事件,immediate,第三个参数表示会不会立即执行,多长时间后执行,比方说输入框每次输入是否隔规定时间触发还是立即触发
和之前一样,timer用来存放定时器的返回值
执行函数内部this的指向,否则定时器的回调中this指向window而不是事件的调用者,事件对象e指向同理
立即执行时,仅仅改变指向你会发现事件依旧被频繁调用,这里我们又需要一个指针,将callNow与timer关联取反,通过对timer变量取反,判断是否执行,通过对延迟timer变量置空来设置下一次的立即执行
再次触发前清空上一次的定时器结果,重新计时
根据对定时器执行结果的判断和是否立即触发事件的条件,确保,当事件触发之后,约定单位时间之后,执行里面的代码,如果在单位时间内再次触发了事件,那么要重新计时,以保证事件里面的代码只执行一次
✍️节流
重点补充下节流,第一版考虑的情况较少,这里我会根据时间戳还有定时器的结果来处理节流
时间戳版
function throttle (func, wait) {
let previousTime = 0
return function () {
const _this = this
const args = arguments
const currentTime = new Date().valueOf()
if (currentTime - previousTime > wait) {
func.call(_this, args)
previousTime = currentTime
}
}
}
根据之前的时间戳和现在的时间戳差值,跟设定的单位之间相比,大于则执行函数,后更新旧的时间戳。注意,此时因为初始时间是0,一开始就会执行,但是最后一次,是没有延迟执行的,所以可以理解成第一次触发,最后一次不触发
定时器版
function throttle (func, wait) {
let timer
return function () {
const _this = this
const args = arguments
if (!timer) {
timer = setTimeout(function () {
func.call(_this, args)
timer = null
}, wait)
}
}
}
保存定时器的返回值,第一次延迟触发,给定单位时间后触发,即第一次不触发,最后一次触发
👷综上
function throttle (func, wait) {
let timer
let previousTime = 0
return function () {
const _this = this
const args = arguments
const currentTime = new Date().valueOf()
if (currentTime - previousTime > wait) {
if (timer) {
clearTimeout(timer)
timer = null
}
func.call(_this, args)
previousTime = currentTime
}
if (!timer) {
timer = setTimeout(function () {
previousTime = new Date().valueOf()
func.call(_this, args)
timer = null
}, wait)
}
}
}
再来!
timer表示定时器的返回值,触发事件时,取出当前时间戳,默认为0
获取当前的时间戳
(大于设置的时间,执行,否则不执行)
判断,如果现在的时间和旧时间差值大于我们设置的等待时间,即立即执行,判断定时器有值清空定时器并置空,然后立即执行,此时将当前的时间定位下一次的旧值
定时器有值的时候,触发定时器计时,间隔时间后才能触发,设置旧时间并置空定时器保证下一次能继续执行
根据时间戳实现第一次会立即触发执行函数,根据定时器实现最后一次离开后即使时间未到规定时间,因为会延迟执行一次定时器,依旧触发最后一次。
当事件触发之后,约定单位时间之内,事件里面的代码最多只能执行 1 次,所以说,所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数,节流会减少函数的执行频率
😝结语
写的不好,有错误的地方烦请各位大佬多多指教,感恩家人感恩🙏
🚩祝各位一路顺风,顶峰相见