在开发过程中,总会遇到函数需要防抖和节流的情况,我将从以下几个方面来刨析防抖和节流
1.什么是防抖和节流
函数防抖
在频繁触发的模式下,我们只识别 “一次” 「识别第一次、也可以只识别最后一次」
函数节流
降低触发的频率,它能识别 “多次”
「浏览器有自己的最快反应时间,例如:谷歌57ms IE1017ms,这样在我们的疯狂操作下,谷歌浏览器的频率是5ms执行一次,节流是降低这个频率,比如我们设定频率是300ms,在疯狂触发的时候,我们控制间隔300ms才让其执行一次」
2.防抖和节流的使用场景
点击事件一般以 防抖
为主「但是有些需求也是节流」
键盘输入事件 或者 滚动条滚动事件 都是以 节流
为主
3.函数防抖debounce
假设fn()
是我们触发的点击事件,debounce()
函数执行后,需要将fn()
执行结果返回
function debounce(func) {
return function proxy() {
func()
}
}
传递的参数中也应该包括wait
频率时间和immediate
是否立即执行
传递参数的过程中可能会出现以下几种情况
debounce(fn)
debounce(fn,300)
debounce(fn,true)
debounce(fn,300,true)
针对这几种情况,需要对传递的参数做以下判断处理
/**
* debounce 函数防抖
* @params
* func[functio]需要执行的函数
* wait[number]延时的时间,默认300
* immediate[boolean]是否立即执行,默认false
* @return
* func执行的结果
*/
function debounce(func,wait,immediate) {
if(typeof func !== 'function') throw new TypeError('func is not a');
if(typeof wait === 'boolean'){
immediate = wait;
wait = 300;
}
if(typeof wait !== 'number') wait = 300;
if(typeof immediate !== 'boolean') immediate = false;
return function proxy() {
func()
}
}
接下来,该处理核心代码,防抖就是在一定时间内不能触发多次只生效一次; 一定时间内,那我们就设置一个定时器,来控制是否执行下一次操作; 但是当我们马上点击第二次的时候,定时器就应该重新设置,再次等待300ms,所以需要做以下处理;
function debounce(func,wait,immediate) {
if(typeof func !== 'function') throw new TypeError('func is not a');
if(typeof wait === 'boolean'){
immediate = wait;
wait = 300;
}
if(typeof wait !== 'number') wait = 300;
if(typeof immediate !== 'boolean') immediate = false;
var timer = null;
return function proxy() {
if(timer)clearTimeout(timer);
timer = setTimeout(function () {
if(timer){
clearTimeout(timer);
timer = null;
}
func();
},wait);
}
}
还差最后一步,需要设置立即执行;如果immediate
为true,那就不需要先执行定时器,而是应该先执行函数func()
,但是需要在判断一下,立即执行是否是在上一个定时器执行完毕。
function debounce(func,wait,immediate) {
if(typeof func !== 'function') throw new TypeError('func is not a');
if(typeof wait === 'boolean'){
immediate = wait;
wait = 300;
}
if(typeof wait !== 'number') wait = 300;
if(typeof immediate !== 'boolean') immediate = false;
var timer = null;
return function proxy() {
var isNow = !timer && immediate;//timer为null代表上一个定时器执行完毕,可以再次“立即执行”
if(timer)clearTimeout(timer);
timer = setTimeout(function () {
if(timer){
clearTimeout(timer);
timer = null;
};
!immediate?func():null;
},wait);
isNow?func():null;
}
}
4.函数节流 throttle
节流是单位时间内触发频率降低,假设fn()
是绑定的页面滚动的触发事件,函数throttle()
最终返回的是fn()
的执行结果。
且对应func
和wait
两个参数的处理
function throttle(func,wait) {
if(typeof func !== 'function') throw new TypeError('func is not an function');
if(typeof wait !== 'number') wait = 300;
return function proxy() {
func();
}
}
throttle()
的核心思想,就是在wait
时间内只执行一次,超过wait
再执行第二次。
设置一个定时器,判断距离wait
时间还剩多少时间。如果小于0,证明已经超过等待时间,可以执行;如果大于0,证明还未等待到时间,需要继续执行定时器。
function throttle(func,wait) {
if(typeof func !== 'function') throw new TypeError('func is not an function');
if(typeof wait !== 'number') wait = 300;
var timer = null,
previous = 0; //之前的时间戳
return function proxy() {
var now = +new Date(), //当前的时间戳
remaining = wait - (now - previous);//距离wait时间还有多少毫秒
if(remaining <= 0){ //超过等待时间,立即执行
func();
}else if(!timer){
timer = setTimeout(function () {//未超过等待时间,先等待剩余时间
func();
}, remaining);
}
}
}
再执行完等待时间后,需要清除timer
,并且将previous
设置为当前的时间。
/**
* thorttle 函数节流
* @params
* func[functio]需要执行的函数
* wait[number]延时的时间,默认300
* @return
* func执行的结果
*/
function throttle(func,wait) {
if(typeof func !== 'function') throw new TypeError('func is not an function');
if(typeof wait !== 'number') wait = 300;
var timer = null,
previous = 0; //之前的时间戳
return function proxy() {
var now = +new Date(), //当前的时间戳
remaining = wait - (now - previous);//距离wait时间还有多少毫秒
if(remaining <= 0){ //超过等待时间,立即执行
if(timer){
clearTimeout(timer);
timer = null;
}
func();
previous = +new Date();
}else if(!timer){
timer = setTimeout(function () {//未超过等待时间,先等待剩余时间
if(timer){
clearTimeout(timer);
timer = null;
}
func();
previous = +new Date();
}, remaining);
}
}
}
防抖debounce()
和节流throttle()
大致思路如上,参考于underscore
和lodash
源码。