一、问题
Popover 弹出框 hover 状态下,鼠标从 triggerWrapper 移到 contentWrapper 气泡依然存在并且没有过渡痕迹。
参考element UI,下面为核心代码(不重要的都删了)
<template>
<div class="popover">
<div ref="contentWrapper">
<slot></slot>
</div>
<span ref="triggerWrapper">
<slot name="reference"></slot>
</span>
</div>
</template>二、函数防抖
概念: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
生活中的实例: 如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。
function debounce(fn, delay){
let timerId = null
return function(){
const context = this
if(timerId){window.clearTimeout(timerId)}
timerId = setTimeout(()=>{
fn.apply(context, arguments)
timerId = null
},delay)
}
}
const debounced = debounce(()=>console.log('hi'))
debounced()
debounced()逻辑:
- 闭包保证timerId是同一个计时器
- 检查timerId是否为空(也可以跳过判断自己创建新计时器)
- 如果是空,开始计时
- 如果不是空,重新开始计时,创建新计时器,并把旧的计时器清除
三、Vue中的使用
data() {
return {
timerId: null
};使用data里的timerId来代替闭包中被隐藏的timerId,因为多次调用debounced函数并不会创建新的timerId
handleMouseLeave() {
window.clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.close();
timerId = null
}, delay);
}, handleMouseEnter(e) {
window.clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.open(e);
timerId = null
}, delay);
},上面是addEventListener的回调函数,具体添加时机看项目细节,该项目中triggerWrapper 和 contentWrapper 都得加
四、为什么防抖可以实现Popover的无缝过渡
因为防抖给鼠标移除时添加计时器,计时器结束气泡消失,如果在计时器时间内移到气泡上,那么创建新计时器,重新计时
五、函数节流
由于找不到使用案例,讲一下原理
概念: 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
function throttle(fn, delay){
let canUse = true
return function(){
if(canUse){
fn.apply(this, arguments)
canUse = false
setTimeout(()=>canUse = true, delay)
}
}
}
const throttled = throttle(()=>console.log('hi'))
throttled()
throttled()逻辑:
- 闭包隐藏canUse保证是同一个变量
- 检查canUse是否为真
- 如果为真,调用函数,并把canUse设置为flase
- 如果为假什么都不做
- 设置计时器,一段时间后变成真
相当于为fn的执行添加CD
function throttle(fn, gapTime) {
let _lastTime = null;
return function () {
let _nowTime = + new Date()
if (_nowTime - _lastTime > gapTime || !_lastTime) {
fn();
_lastTime = _nowTime
}
}
}节流的另一个实现方式
六、总结
节流触发时间总是你设置好的时间,而防抖执行时间不确定