Bootstrap

节流与防抖

防抖和节流是针对响应跟不上触发频率这类问题的两种解决方案。 在给DOM绑定事件时,有些事件我们是无法控制触发频率的。 如鼠标移动事件onmousemove, 滚动滚动条事件onscroll,窗口大小改变事件onresize,瞬间的操作都会导致这些事件会被高频触发。 如果事件的回调函数较为复杂,就会导致响应跟不上触发,出现页面卡顿,假死现象。 在实时检查输入时,如果我们绑定onkeyup事件发请求去服务端检查,用户输入过程中,事件的触发频率也会很高,会导致大量的请求发出,响应速度会大大跟不上触发。

针对此类快速连续触发和不可控的高频触发问题,debounce 和 throttling 给出了两种解决策略;

debounce,去抖动。策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。 这是debounce的基本思想,在后期又扩展了前缘debounce,即执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。

延迟debounce,示意图:

aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM4MTI3NDY2,size_16,color_FFFFFF,t_70)

前缘debounce, 示意图

在这里插入图片描述

debounce的特点是当事件快速连续不断触发时,动作只会执行一次。 延迟debounce,是在周期结束时执行,前缘debounce,是在周期开始时执行。但当触发有间断,且间断大于我们设定的时间间隔时,动作就会有多次执行。

debounce 的实现:

版本1: 周期内有新事件触发,清除旧定时器,重置新定时器;这种方法,需要高频的创建定时器。

// 暴力版: 定时器期间,有新操作时,清空旧定时器,重设新定时器
var debounce = (fn, wait) => {
let timer, timeStamp=0;
let context, args;

let run = ()=>{
	timer= setTimeout(()=>{
		fn.apply(context,args);
	},wait);
}
let clean = () => {
	clearTimeout(timer);
}

return function(){
	context=this;
	args=arguments;
	let now = (new Date()).getTime();

	if(now-timeStamp < wait){
		console.log('reset',now);
		clean();  // clear running timer 
		run();    // reset new timer from current time
	}else{
		console.log('set',now);
		run();    // last timer alreay executed, set a new timer
	}
	timeStamp=now;

}

}
版本2: 周期内有新事件触发时,重置定时器开始时间撮,定时器执行时,判断开始时间撮,若开始时间撮被推后,重新设定延时定时器。

/ 优化版: 定时器执行时,判断start time 是否向后推迟了,若是,设置延迟定时器
var debounce = (fn, wait) => {
let timer, startTimeStamp=0;
let context, args;

let run = (timerInterval)=>{
	timer= setTimeout(()=>{
		let now = (new Date()).getTime();
		let interval=now-startTimeStamp
		if(interval<timerInterval){ // the timer start time has been reset, so the interval is less than timerInterval
			console.log('debounce reset',timerInterval-interval);
			startTimeStamp=now;
			run(timerInterval-interval);  // reset timer for left time 
		}else{
			fn.apply(context,args);
			clearTimeout(timer);
			timer=null;
		}
		
	},timerInterval);
}

return function(){
	context=this;
	args=arguments;
	let now = (new Date()).getTime();
	startTimeStamp=now;

	if(!timer){
		console.log('debounce set',wait);
		run(wait);    // last timer alreay executed, set a new timer
	}
	
}

}
版本3: 在版本2基础上增加是否立即执行选项:

// 增加前缘触发功能
var debounce = (fn, wait, immediate=false) => {
let timer, startTimeStamp=0;
let context, args;

let run = (timerInterval)=>{
	timer= setTimeout(()=>{
		let now = (new Date()).getTime();
		let interval=now-startTimeStamp
		if(interval<timerInterval){ // the timer start time has been reset,so the interval is less than timerInterval
			console.log('debounce reset',timerInterval-interval);
			startTimeStamp=now;
			run(timerInterval-interval);  // reset timer for left time 
		}else{
			if(!immediate){
				fn.apply(context,args);
			}
			clearTimeout(timer);
			timer=null;
		}
		
	},timerInterval);
}

return function(){
	context=this;
	args=arguments;
	let now = (new Date()).getTime();
	startTimeStamp=now; // set timer start time

	if(!timer){
		console.log('debounce set',wait);
		if(immediate) {
			fn.apply(context,args);
		}
		run(wait);    // last timer alreay executed, set a new timer
	}
	
}

}

throttling,节流的策略是,固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。 节流策略也分前缘和延迟两种。与debounce类似,延迟是指 周期结束后执行动作,前缘是指执行动作后再开始周期。

延迟throttling示意图:

在这里插入图片描述

前缘throttling 示意图:
在这里插入图片描述

throttling的特点在连续高频触发事件时,动作会被定期执行,响应平滑。

throttling 的实现:

版本1: 简单版

简单版: 定时器期间,只执行最后一次操作
var throttling = (fn, wait) => {
let timer;
let context, args;

let run = () => {
	timer=setTimeout(()=>{
		fn.apply(context,args);
		clearTimeout(timer);
		timer=null;
	},wait);
}

return function () {
	context=this;
	args=arguments;
	if(!timer){
		console.log("throttle, set");
		run();
	}else{
		console.log("throttle, ignore");
	}
}

}
版本2: 增加前缘选项:(考虑情况较简单,复杂情况可参考underscope 的_.throttle)

/// 增加前缘
var throttling = (fn, wait, immediate) => {
let timer, timeStamp=0;
let context, args;

let run = () => {
	timer=setTimeout(()=>{
		if(!immediate){
			fn.apply(context,args);
		}
		clearTimeout(timer);
		timer=null;
	},wait);
}

return function () {
	context=this;
	args=arguments;
	if(!timer){
		console.log("throttle, set");
		if(immediate){
			fn.apply(context,args);
		}
		run();
	}else{
		console.log("throttle, ignore");
	}
}

}

debounce和throttling 各有特点,在不同 的场景要根据需求合理的选择策略。如果事件触发是高频但是有停顿时,可以选择debounce; 在事件连续不断高频触发时,只能选择throttling,因为debounce可能会导致动作只被执行一次,界面出现跳跃。


作者:起晚的蜗牛
来源:CSDN
原文:https://blog.csdn.net/hupian1989/article/details/80920324
版权声明:本文为博主原创文章,转载请附上博文链接!

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;