防抖(debounce) 和 节流(throttling)

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

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

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

延迟debounce,示意图:

前缘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(wait-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(wait-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可能会导致动作只被执行一次,界面出现跳跃。

 

  • 148
    点赞
  • 650
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论
Vue和Loadsh在前端开发中非常常用,在前端开发过程中常常遇到需要防抖节流的情况。下面我们将具体介绍Vue和Loadsh对事件防抖节流的实现。 事件防抖是指在短时间内多次触发同一事件,只执行最后一次触发的事件。这种现象常见于浏览器不断地触发resize、scroll、mousemove等多种事件。为了避免短时间内多次执行同一事件而导致的浪费资源和卡顿现象,我们通常会采用事件防抖的方式来避免这种情况的出现。 Vue通过@keyup、@keydown、@input等事件绑定方式可以实现事件防抖。例如: <template> <div> <input type="text" v-model="value" @keyup="debounceHandle"/> <div>{{ value }}</div> </div> </template> <script> import debounce from 'loadash/debounce'; export default { data () { return { value: '' } }, methods: { debounceHandle: debounce(function() { console.log('事件防抖'); }, 500) } } </script> 上述代码中,我们使用loadash/debounce函数,这个函数会返回一个debounced函数。在此处,我们将debounced函数绑定到@keyup事件上,设置延迟时间为500毫秒。这样在输入框输入的时候,每次输入之后都会等待500毫秒后执行该函数,确保不会多次执行该函数。 事件节流是指在一段时间内,多次触发同一事件,但只执行一次函数。相比于事件防抖,事件节流把触发的事件按照一定规律分为多个时间段,在一个时间段内只执行一次函数。 Vue和Loadsh的throttle函数可以实现事件节流。例如: <template> <div> <input type="text" v-model="value" @keyup="throttleHandle"/> <div>{{ value }}</div> </div> </template> <script> import throttle from 'loadash/throttle'; export default { data () { return { value: '' } }, methods: { throttleHandle: throttle(function() { console.log('事件节流'); }, 500) } } </script> 上述代码中,我们使用loadash/throttle函数,这个函数会返回一个throttled函数。在此处,我们将throttled函数绑定到@keyup事件上,设置间隔时间为500毫秒。这样在输入框输入的时候,在500毫秒时间内只执行一次函数。 综上所述,事件防抖和事件节流是前端开发中非常常用的技术,通过Vue和Loadsh的配合可以轻松实现事件防抖和事件节流。在实际开发过程中,需要根据具体情况选择使用防抖节流技术。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起晚的蜗牛

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值