第一次听到这个东西是毕业后去头条面试的时候,面试说:说一下函数防抖是什么,wdt,啥玩意儿?只能默默的回答:不好意思,这个不是很熟!尴尬ing......
工作后一直在业务团队,真正用到这个东西的次数也不多,但是,我们得懂!一切的业务开发都要跟高标(mian)准(shi)靠齐,不然有啥子意思勒~
是啥玩意呢?
防抖和节流都是防止函数多次调用,在时间轴上控制函数的执行次数。
函数防抖(debounce)
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时,先计算时间后执行。
生活中的实例:有人进入电梯,电梯则在10秒后自动关门,如果10秒内又有人进入电梯,则电梯重新计时,自动关门。
function debounce(fn, delayTime) {
let timer = null
return function (args) {
if (timer) {
timer = null
clearTimeout(timer)
}
let _this = this
let _args = args
timer = setTimeout(function () {
fn.call(_this, _args)
}, delayTime)
}
}
let inputDom = document.getElementById('input2')
inputDom.addEventListener('keyup', debounce((e) => {console.log(e.target.value)}, 1000))
防抖原理
为了减少频繁发生的事件的资源请求,防抖是通过在用户没有操作的情况下,如果在等待时间内用户没有操作,则执行回调,如果有操作,继续等待相应时间间隔。所以在闭包函数内,初始化一个时间参数timer,如果timer为空,则表示回调事件没执行或者已执行完毕,代表在等待时间内用户没有操作,相反,如果timer不为null,清空timer,重新计时。
那如果我们想要先执行回调函数再计时,而不是时间到了再执行呢?也可以实现立即执行版:
function debounce(fn, delayTime, immediate) {
let timer = null
return function () {
if (timer) {
timer = null
clearTimeout(timer)
}
let _this = this
let _args = arguments
if (immediate) {
timer = setTimeout(() => {
timer = null
}, delayTime)
if (!timer) { fn.call(_this, _args) }
} else {
timer = setTimeout(function () {
fn.call(_this, _args)
}, delayTime)
}
}
}
函数节流(throttle)
n秒内回调函数只会被执行一次,先执行后计算。
生活中的实例:玩飞机大战时,点击屏幕连发子弹,即使点击屏幕的频率再快,子弹也是平均间隔时间发射。
setTimeout实现函数节流
function throttle(fn, wait) {
var timer = null
if (timer) return
return function() {
let _this = this
let args = arguments
timer = setTimeout(function() {
fn.apply(_this, args) timer = null
},
wait)
}
}
时间差实现函数节流
function throttle(fn, wait) {
var lastTime = 0
return function() {
let _this = this
let args = arguments
let nowTime = new Date()
if (nowTime - lastTime > wait) {
fn.apply(_this, args) lastTime = nowTime
}
}
}
节流原理
函数节流的目的是为了让函数在特定时间内疚执行一次,这也是setTimeout的设计初衷,所以可以直接用setTimeout实现,当然执行完回调一定要将timer置空。另外一种思路就是计算触发时间和执行时间的差值,大于再执行回调。
使用场景
总结一下:函数防抖就是特定时间内触发动作的话,重新计时。函数节流是特定时间内触发动作只会有一次回调。
所以函数防抖适用的场景:监听窗口的滚动,缩放。高频发生的一些事件;
函数节流适用的场景:涉及与后端交互的按钮,由于网络原因或者其他原因,导致接口没有返回值,用户一直点点点的问题。
VUE 中使用函数防抖
业务需求中,有一个功能是点击按钮,执行sdk方法,吊起系统的拍照功能,在吊起的时候,会存在时间差,为了防止用户频繁点击,需要加上防抖的功能。
<template>
<div @click="uploadPic">点击吊起系统拍照</div>
</template>
methods: {
uploadPic: vueDebounce('wakeUpPhotos', 2000, true),
wakeUpPhotos () {
// todo 执行sdk方法,及回调逻辑
}
}
vueDebounce (fnName, time, immediate) {
let timer = null
return function () {
let args = arguments
if (timer) { clearTimeout(timer) }
// 判断是否需要立即执行
if (immediate) {
timer = setTimeout(() => {
timer = null
}, time)
if (!timer) { this[fnName](...args) }
} else {
timer = setTimeout(() => {
this[fnName](...args)
}, time)
}
}
}
**fnName**,是vue中methods的名称,不可以直接传回调函数,是因为this指向会不对- 执行回调的时候
this指的是当前创建的vue对象,所以可以取到fnName对应的回调方法 - 由于业务需求,所以一般都会加上立即执行的功能~