节流和防抖是什么
节流和防抖是优化高频率执行代码的一种方式,像项目中会涉及到的监听浏览器的resize、scroll、keypress、mousemove等事件在触发时,会不断地调用绑定在事件上的回调函数,但实际上这么频繁的调用会降低前端性能,故我们可以对这类事件进行调用次数的限制,通过节流(throttle) 和 防抖(debounce) 的方式来减少调用频率。
-
相同点
目的都是降低回调执行频率,节省性能资源,都可以通过定时器实现, -
不同点
节流是指设定一个时间周期,在这个时间周期内如果连续执行某操作,只有一次操作能生效,受时间周期限制
防抖同样会设定一个时间周期,但操作生效的时间点不受时间周期的限制,连续执行某操作,只有一次操作生效,即使操作生效的时间点超出时间周期范围
下面示例会用到的公共代码片段
<button>点我</button>
function a(n) {
console.log(n)
}
一、节流 (throttle)
固定周期内,即使多次触发事件,也只执行一次动作;周期结束后,再次触发事件,开始新的周期。
根据事件触发的时间点可分为延迟节流和前缘节流
1.延迟节流 指周期结束后执行动作
定时器写法
简易初版
let flag = true;
const throttling = (fn,param delay) => {
if (flag) {
flag = false;
setTimeout(() => {
fn(...param);
flag = true;
}, delay)
}
}
document.getElementsByTagName("button")[0].addEventListener("click", () => {
throttling(a,[2] 3000)
})
使用闭包优化后
function throttling(fn, delay) {
// timer的作用域在外层函数作用域
let timer = null;
return (n) => {
if (!timer) {
timer = setTimeout(() => {
fn.call(this, n);
clearTimeout(timer)
timer = null;
}, delay)
}
}
}
const b = throttling(a,3000);
document.getElementsByTagName("button")[0].addEventListener("click", () => {
b(2)
})
2.前缘节流 指执行动作后再开始周期
定时器写法
function throttling(fn, delay) {
// timer的作用域在外层函数作用域
let timer = null;
return (n) => {
if (!timer) {
fn.call(this, n);
timer = setTimeout(() => {
clearTimeout(timer)
timer = null;
}, delay)
}
}
}
const b = throttling(a, 3000,true);
document.getElementsByTagName("button")[0].addEventListener("click", () => {
b(2)
})
时间戳写法
function throttling(fn, delay) {
// timer的作用域在外层函数作用域
let oldtime = 0;
return (n) => {
let newtime = Date.now();
if(newtime - oldtime > delay){
fn.call(this,n);
oldtime = newtime;
}
}
}
3.延迟节流和前缘节流统一处理
形参immediate控制周期开始/结束时执行动作
function throttling(fn, delay, immediate = false) {
// timer的作用域在外层函数作用域
let timer = null;
return (n) => {
if (!timer) {
immediate && fn.call(this, n);
timer = setTimeout(() => {
!immediate && fn.call(this, n);
clearTimeout(timer)
timer = null;
}, delay)
}
}
}
const b = throttling(a, 3000,true);
document.getElementsByTagName("button")[0].addEventListener("click", () => {
b(2)
})
二、防抖 (debounce)
同样,根据事件触发的时间点可分为分为延迟防抖和前缘防抖
1.延迟防抖
当事件触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作
function debounce(fn, delay) {
// timer的作用域在外层函数作用域
let timer = null;
return (n) => {
timer && clearInterval(timer);
timer = setTimeout(() => {
fn.call(this, n);
clearTimeout(timer);
timer = null;
}, delay);
};
}
const b = debounce(a, 3000);
document.getElementsByTagName("button")[0].addEventListener("click", () => {
b(2);
});
2.前缘防抖
执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定
function debounce(fn, delay) {
// timer的作用域在外层函数作用域
let timer = null;
return (n) => {
!timer && fn.call(this, n);
timer && clearInterval(timer);
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
}, delay);
};
}
const b = debounce(a, 3000,true);
document.getElementsByTagName("button")[0].addEventListener("click", () => {
b(2);
});
3.延迟防抖和前缘防抖统一处理
形参immediate控制周期开始/结束时执行动作
function debounce(fn, delay, immediate = false) {
// timer的作用域在外层函数作用域
let timer = null;
return (n) => {
immediate && !timer && fn.call(this, n);
timer && clearInterval(timer);
timer = setTimeout(() => {
!immediate && fn.call(this, n);
clearTimeout(timer);
timer = null;
}, delay);
};
}
const b = debounce(a, 3000,true);
document.getElementsByTagName("button")[0].addEventListener("click", () => {
b(2);
});
应用场景
1.防抖
input输入框,只需用户最后一次输入完,显示输入结果,或检测正确
浏览器窗口resize事件,在用户进行一连串调整窗口的动作后,再计算需要的信息,防止重复渲染
2.节流
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
鼠标点击事件不断触发,单位时间内只执行一次
vue自定义指令
前缘节流
<el-button type="text" v-throttle="{ fn: onAddRecord, param: [item], time: 5*1000 }">按我</el-button>
-------------------
Vue.directive('throttle', throttle)
--------------------
/**
* 节流
*/
export const throttle = {
bind(el, binding, vnode){
let {fn, param = [], event = "click", time = 1000} = binding.value;
let timer = null;
el.addEventListener(event, () => {
if(!timer){
fn(...param);
timer = setTimeout(()=>{
clearTimeout(timer);
timer = null;
},time)
}
})
}
前缘防抖
<div v-debounce="() => { checkBindRole(roleMsg); } ">确认</div>
--------------------
Vue.directive("debounce", {
inserted: function(el, binding, vnode) {
let {fn, param = [], event = "click", time = 1000} = binding.value;
let timer = null;
el.addEventListener(event, () => {
timer && clearTimeout(timer);
//binding.value()即调用传进来的方法
!timer && fn(...param);
timer = setTimeout(() => {
timer = null;
}, time);
});
},
});