手撕函数的防抖和节流

40 阅读2分钟

防抖和节流的区别

防抖:在频繁触发的操作中,每次点击重置计时。 节流:在频繁触发的操作中,依然按照给定的时间去触发,降低触发的频率。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="submit">点我打印数据</button>
</body>
<script>
  let btn = document.querySelector('#submit');


  function handle(ev) {
    console.log('ok', ev, this);
  }

  btn.onclick = handle;
</script>
</html>

点击按钮输出如下

// ok  MouseEvent {...}  <button id="submit">点我打印数据</button>

防抖的实现

注意修正 event 对象 和 this 哦~

/*  debounce: 函数防抖
 *		@params
 *      	func: 最终要执行的任务代码
 * 			wait: 多久操作一次算是频繁触发「默认值:500ms」
 *          immediate: 刚开始就触发 「默认值:false,结束时触发」
 *      @return
 *  		operate 处理函数
 */
const clearTimer = function(timer) {
	if (timer) {
		clearTimeout(timer);
		timer = null;
	}

	return null;
}

const debounce = function (func, wait, immediate) {
	if (typeof func !== 'function') {
		throw new TypeError('func must be an function!');
	}
	// 如果第二个参数传的是个布尔 赋值给第三个参数
	if (typeof wait === 'boolean') immediate = wait; 
	if (typeof wait !== 'number') wait = 500;
	if (typeof immediate !== 'boolean') immediate = false;

	let timer = null;

	return function oprate(...params) {
	    // 首次调用「或定时器结束重置为首次调用」 且 立即执行
	    let nowRun = !timer && immediate; 

		// 如果定时器存在 再次触发 清掉计时 重新计算
		timer = clearTimer(timer);

		timer = setTimeout(() => {
      	  // 首次点击就调用的 不执行该方法
			!nowRun && func.call(this, ...params); // 修正 event 对象
      	  timer = clearTimer(timer); // 清空,为了重新触发 nowRun = true
		}, wait);

	    // 定时器不存在(刚开始点击) 立即执行一次
	    nowRun && func.call(this, ...params);
	}
}

// btn.onclick = debounce(handle); // 非立即执行版本
btn.onclick = debounce(handle, true); // 立即执行版本

节流的实现

节流一般不控制立即执行,默认就是立即执行一次。
/*  debounce: 函数节流
 *		@params
 *      	func: 最终要执行的任务代码
 * 			wait: 触发频率
 *      @return
 *  		operate 处理函数
 */
const clearTimer = function(timer) {
	if (timer) {
		clearTimeout(timer);
		timer = null;
	}

	return null;
}

const throttle = function(func, wait) {
	if (typeof func !== 'function') {
		throw new TypeError('func must be an function!');
	}

	if (typeof wait !== 'number') wait = 500;

	let timer = null;
	let prevTime = 0; // 上次触发时间

	return function oprate(...params) {
		let now = +new Date();
		let nextRunTime = wait - (now - prevTime); // 下次执行时间

		if (nextRunTime <= 0) {
			// 两次间隔时间超过 500ms 第一次是 +new Date() - 0
			// 立即执行
			func.call(this, ...params);
			prevTime = +new Date();
			clearTimer(timer);
		} else if (!timer) {
			// 没设置过定时器等待(上一轮执行过了),则我们设置一个去等待即可
			timer = setTimeout(() => {
				func.call(this, ...params);
				prevTime = +new Date();
				clearTimer(timer);
			}, nextRunTime);
		}
	}
}

btn.onclick = throttle(handle, 1000);