web前端高级JavaScript - 函数的防抖和节流

653 阅读5分钟

函数的防抖

函数的防抖:就是对于一些 ==频繁== 的操作,在 ==一定的时间== 内只 ==执行一次== (第一次或最后一次)。 场景描述:比如页面中的按钮点击事件,一般情况下会给按钮绑定一个点击事件,然后当我们点击按钮时就会触发事件并执行某些功能代码(比如请求后台api)。正常情况我们都是点击一下触发一次,但是不排除有人恶意不停的点击按钮,这样的话就会不停的去请求后台api,势必会给服务器造成一定的压力,所以为了防止这种频繁的操作,函数的防抖就诞生了。 实现思路:利用闭包机制

  • 需要预先设定一个等待时间(等待这段时间内仅执行一次代码) wait,设置定时器用于计时
  • 当我们第一次点击按钮触发事件时开始计时(这里需要一个定时器),等待wait这么长时间
  • 在这等待期间看是否有第二次事件触发,如果没有第二次触发,则等待时间结束后自动执行功能代码
  • 如果这等待期间第二次事件又被触发,则清除原来的定时器,重新开始计时重新等待wait这么长时间,并且代码不会执行直到在这段时间内不再频繁触发,代码才会执行。

使用场景:一般用于按钮点击事件

函数语法: @params

  • func[function]:要触发的函数,真正执行功能代码的函数
  • wait[number]:等待时间
  • immediate[boolean]:这里可以加个开关用于识别:是频繁操作的第一次执行还是最后一次执行

@return

  • 可以被调用执行的函数
<button id="submit"></button>
//默认识别第一次,就是在该段时间内,只有第一次点击生效。
function debounce(func, wait = 300, immediate = true){
	let timer = null;
	return function anonymouse(...params){
		//每次点击都要先清除定时器重新计时wait
		clearTimeout(timer);
		//只要timer不为null,则说明是频繁点击,是频繁点击就需要重新计算并且函数func只需执行一次
		//一旦频繁点击结束,等待时间wait到了,则timer又会被重新置为null
		let now = immediate && !timer;
		timer = setTimeout(() => {
			//等待时间到了以后需要重新将timer置为null,便于识别是否是下一轮频繁点击的第一次点击
			timer = null;
			//这里之所以用这种方式调用func,是不想将原函数(func)中的this指向改变
			//当前这个匿名函数时由按钮触发,所以这里this指向的就是触发事件的那个按钮。
			//而如果不用函数的防抖机制,直接把函数func绑定给按钮,则在func中的this指向也是触发事件的按钮。
			//所以这里如果直接调用func函数的话,func中的this指向就变为window了。
			//如果immediate为true则在第一次触发就已经执行,这里等待时间到就不再执行了
			!immediate ? func.call(this, ...params) : null;
		},wait);
		
		//如果immediate为true说明是需要识别第一次触发,则调用函数执行
		now ? func.call(this, ...params) : null;
	}
}

function handle(){
	console.log(this);
	//调用API.....
}

submit.onclick = debounce(handle, 300, true);

函数的节流

函数的节流:节流就是在一段频繁的操作中,为了不让代码频繁的执行,预设一个时间,然后让代码每隔预设的这段时间执行一次。 场景描述:比如页面滚动事件,当我们在拖动滚动条时,对应的滚动条事件中的代码就会不停的执行,每一次滚动过程中,浏览器最快反应时间一般为5~6ms,而只要过了5 ~6ms,对应的函数就会被触发执行。所以 为了让代码执行的不那么频繁,我们会采用函数的节流来控制。 实现思路:利用闭包机制

  • 需要预先设定一个等待时间(每隔多长时间可以执行一次代码) wait,设置定时器timer用于计时
  • 当滚动条刚开始滚动第一次执行时,记下当前时间now,并将上一次执行时间previous设置为0,这样能保证刚开始滚动时肯定能执行一次
  • 用间隔时间wait减去当前时间now再减去上次执行时间previous,就是离我预设的时间还剩余多长时间remaining
  • 如果剩余时间remaining小于等于0,则说明两次函数执行的时间间隔已经过了我们预设的间隔时间,则可以进行下一次执行 调用具体执行函数func,同时将当前时间now赋值给previous作为上一次执行时间
  • 如果剩余时间remaining大于0 并且定时器为空(没设置或手动清空过),则说明间隔时间还没到,并且也没有设置计时器倒计时;这时就需要设置一个定时器开始倒计时,倒计时的时间就是剩余时间remaining,如果到计时结束了则可以再次执行代码,同时将定时器timer清空,便于下次设置定时器;然后将当前执行时间赋值给previous作为上一次执行时间待用。

使用场景:一般用于页面滚动、文本框输入过程中的模糊匹配

函数语法: @params

  • func[function]:要触发的函数,真正执行功能代码的函数
  • wait[number]:间隔时间

@return

  • 可以被调用执行的函数
function throttole(func, wait){
	let timer = null,
		previous = 0;
	return function anonmouse(...params){
		let now = new Date(),
			remaining = wait - (now - previous);
		if(remaining <= 0){//两次执行时间已经超过设置的间隔时间了,可以执行了
			//将当前执行时间作为上次执行时间赋值给previous
			previous = now;
			//取消倒计时,因为这里已经执行了
			clearTimeout(timer);
			//执行完成后清空timer,便于下次设置倒计时定时器
			timer = null;
			func.call(this, ...params);
		}else if(!timer){
		//两次执行时间差还不到预设的间隔时间,并且也没有设置定时器倒计时;
		//则设置定时器开始倒计时,时间结束就又可以执行了
		//如果已经设置了定时器,就不再做任何处理了。
			timer = setTimeout(() => {
				//执行完成后清空timer,便于下次设置倒计时定时器
				timer = null;
				//将当前执行时间作为上次执行时间赋值给previous
				previous = new Date();				
				func.call(this, ...params);
			}, remaining);
		}
	}
}


function handle(){
	console.log(this);
	//dosomthing.....
}

document.body.onscroll = throttole(handle, 500);