JavaScript 防抖
2023-03-27 09:55:02
70

文章封面

防抖(debounce)

概念

其概念其实是从机械开关和继电器的“去弹跳”(debounce)衍生出来的,基本思路就是把多个信号合并为一个信号。在程序中防抖的作用是 只有在某个时间内没有再次触发函数时,才真正的调用这个函数,从而优化交互与性能。

场景

  1. 输入框内容改变触发提交时,高频输入
  2. 频繁点击按钮触发事件
  3. 监听浏览器滚动
  4. window resize 事件
  5. ...

实现

场景1为例,减少持续输入触发的高频次请求,缓解服务器压力.

基本实现

<body>
	<input type="text" />
</body>
<script>
	const inputEl = document.querySelector('input');

	function debounce(fn, delay) {
		/*
		timer:定时器实例
		由于闭包原理,同一条作用域链上重复调用debounce()内部的timer不会初始化,
		所以能保存上一次创建的定时器实例,从而能在下一次防抖时,取消上一次请求
		*/
		let timer = null;
		const _debounce = function() {
			if (timer) clearTimeout(timer);
			timer = setTimeout(() => {
				fn();
				timer = null;
			}, delay);
		}
		return _debounce;
	}

	let counter = 0;
	const inputChange = function() {
		console.log(`发送了 ${++counter} 次网络请求`);
	}

	inputEl.oninput = debounce(inputChange, 1000);

	/*
	不使用防抖的情况下,每输入一个字符,就会触发一次请求
	inputEl.oninput = inputChange;
	*/
</script>

优化this指向和参数

<body>
	<input data-id="9527" type="text" />
</body>
<script>
	const inputEl = document.querySelector('input');

	function debounce(fn, delay) {
		let timer = null;
		const _debounce = function() {
			if (timer) clearTimeout(timer);
			timer = setTimeout(() => {
				/*
				回调函数中 this 指向上下文 window
				inputEl.oninput 实际触发 _debounce 它的指向是 inputEl
				将回调函数 this 指向到DOM对象 inputEl
				回调函数将接收到输入框的事件对象 event
				*/
				fn.apply(this, arguments);
				timer = null;
			}, delay);
		}
		return _debounce;
	}

	let counter = 0;
	const inputChange = function(event) {
		console.log(`发送了 ${++counter} 次网络请求`);
		console.log(this, event);
	}

	inputEl.oninput = debounce(inputChange, 1000)
</script>

首次立即执行

<body>
	<input data-id="9527" type="text" />
</body>
<script>
	const inputEl = document.querySelector('input');

	function debounce(fn, delay, immediate = false) {
		let timer = null;
		/*
		是否已经执行
		与 timer 同理,只会初始化一次
		*/
		let isInvoke = false;

		const _debounce = function() {
			// 首次执行并且开启了立即执行
			if (immediate && !isInvoke) {
				fn.apply(this, arguments);
				// 标记已经执行
				isInvoke = true;
			} else {
				if (timer) clearTimeout(timer);
				timer = setTimeout(() => {
					fn.apply(this, arguments);
					// 标记已经执行
					isInvoke = true;
					timer = null;
				}, delay);
			}
		}
		return _debounce;
	}

	let counter = 0;
	const inputChange = function(event) {
		console.log(`发送了 ${++counter} 次网络请求`);
		console.log(this, event);
	}

	inputEl.oninput = debounce(inputChange, 1000, true)
</script>

取消执行

<body>
	<input data-id="9527" type="text" />
	<button id="cancel">取消</button>
</body>
<script>
	const inputEl = document.querySelector('input');
	const btn = document.getElementById('cancel')

	function debounce(fn, delay, immediate = false) {
		let timer = null;
		let isInvoke = false;

		const _debounce = function() {
			if (immediate && !isInvoke) {
				fn.apply(this, arguments);
				isInvoke = true;
			} else {
				if (timer) clearTimeout(timer);
				timer = setTimeout(() => {
					fn.apply(this, arguments);
					isInvoke = true;
					timer = null;
				}, delay);
			}
		}
		// 取消执行的函数(清除定时器和修改isInvoke即可)
		_debounce.cancel = function() {
			clearTimeout(timer)
			isInvoke = false;
			timer = null;
		}
		return _debounce;
	}

	let counter = 0;
	const inputChange = function(event) {
		console.log(`发送了 ${++counter} 次网络请求`);
		console.log(this, event);
	}
	const _debounce = debounce(inputChange, 1000, true)
	
	// 输入框的input事件
	inputEl.oninput = _debounce

	// 取消按钮的点击事件
	btn.onclick = function() {
		_debounce.cancel()
	}
</script>

如有帮助,点赞鼓励一下吧!
评论
一键登录