js函数的防抖(debounce)与节流(throttle)-测试案例

公共方法

const btn = document.getElementById("btn");
const btn2 = document.getElementById("btn2");
const btn3 = document.getElementById("btn3");

function Money(){
	console.log("点击了按钮",this);
}

函数防抖-debounce

测试demo-看控制台

页面结构

<button id="btn">非立即执行版 按钮</button>
<button id="btn2">非立即执行版 按钮</button>
<button id="btn3">合成版 按钮</button>

短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行

非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间

// 非立即执行版
function debounce(fun,delay){
	let timer;
	return function (){
		let context = this; // 注意 this 指向
		let args = arguments; // arguments中存着e
		// 清除定时器
		if (timer) clearTimeout(timer);
		
		timer = setTimeout(()=>{
			// 绑定this
			// fun.call(context,args);
			fun.apply(this, args);
		},delay);
	}
}

立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果

// 立即执行版
function debounces(fun, wait) {
	let timer;
	return function() {
	  let context = this; // 这边的 this 指向谁?
	  let args = arguments; // arguments中存着e
 
	  if (timer) clearTimeout(timer);
 
	  let callNow = !timer;
 
	  timer = setTimeout(() => {
		timer = null;
	  }, wait)
 
	  if (callNow) fun.apply(context, args);
	}
}

合成版

// 合成版
/**
   * @desc 函数防抖
   * @param fun 目标函数
   * @param wait 延迟执行毫秒数
   * @param immediate true - 立即执行, false - 延迟执行
   */
function debouncehc(fun, wait, immediate) {
	let timer;
	return function() {
	  let context = this,
		  args = arguments;
		   
	  if (timer) clearTimeout(timer);
	  if (immediate) {
		let callNow = !timer;
		timer = setTimeout(() => {
		  timer = null;
		}, wait);
		if (callNow) fun.apply(context, args);
	  } else {
		timer  = setTimeout(() => {
		  fun.apply(context, args);
		}, wait)
	  }
	}
}

使用

btn.addEventListener("click",debounce(Money,1000));
btn2.addEventListener("click",debounces(Money,1000));
btn3.addEventListener("click",debouncehc(Money,1000,false));


节流-throttle

测试demo-看控制台

指连续触发事件但是在 n 秒中只执行一次函数。即 2n 秒内执行 2 次... 。节流如字面意思,会稀释函数的执行频率

页面结构

<button id="btn">时间戳版 按钮</button>
<button id="btn2">定时器版 按钮</button>
<button id="btn3">合成版 按钮</button>

时间戳版

持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次

// 时间戳版
function throttle(fun, wait) {
	let previous = 0;
	return function() {
	  let now = Date.now();
	  let context = this;
	  let args = arguments;
	  if (now - previous > wait) {
		fun.apply(context, args);
		previous = now;
	  }
	}
}

定时器版

持续触发事件的过程中,函数不会立即执行,并且每 1s 执行一次,在停止触发事件后,函数还会再执行一次

// 定时器版
function throttles(fun, wait) {
	let timeout;
	return function() {
	  let context = this;
	  let args = arguments;
	  if (!timeout) {
		timeout = setTimeout(() => {
		  timeout = null;
		  fun.apply(context, args)
		}, wait)
	  }
	}
}

区别就是,时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候。

合并版

/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */
function throttlehc(func, wait, type) {
	let previous;
	let timeout;
	if (type === 1) {
		previous = 0;
	} else if (type === 2) {
		timeout;
	}
	return function() {
		let context = this;
		let args = arguments;
		if (type === 1) {
			let now = Date.now();

			if (now - previous > wait) {
				func.apply(context, args);
				previous = now;
			}
		} else if (type === 2) {
			if (!timeout) {
				timeout = setTimeout(() => {
					timeout = null;
					func.apply(context, args)
				}, wait)
			}
		}
	}
}

使用

btn.addEventListener("click",throttle(Money,1000));
btn2.addEventListener("click",throttles(Money,1000));
btn3.addEventListener("click",throttlehc(Money,1000,2));


关于节流/防抖函数中 context(this) 的指向解析

首先,在执行  throttle(count, 1000) 这行代码的时候,会有一个返回值,这个返回值是一个新的匿名函数,因此  content.onmousemove = throttle(count,1000); 这句话最终可以这样理解

content.onmousemove = function() {
    let now = Date.now();
    let context = this;
    let args = arguments;
    ...
    console.log(this)
}

到这边为止,只是绑定了事件函数,还没有真正执行,而 this 的具体指向需要到真正运行时才能够确定下来。
所以这个时候如果我们把前面的  content.onmousemove  替换成  var fn  并执行 fn  fn()  ,此时内部的 this 打印出来就会是 window 对象。
其次,当我们触发 onmousemove 事件的时候,才真正执行了上述的匿名函数,即  content.onmousemove()  。
此时,上述的匿名函数的执行是通过  对象.函数名()  来完成的,那么函数内部的 this 自然指向 对象。
最后,匿名函数内部的 func 的调用方式如果是最普通的直接执行  func()  ,那么 func 内部的 this 必然指向 window ,虽然在代码简单的情况下看不出什么异常(结果表现和正常一样),但是这将会是一个隐藏 bug,不得不注意啊!
所以,我们通过匿名函数捕获 this,然后通过 func.apply() 的方式,来达到 content.onmousemove = func 这样的效果。
可以说,高阶函数内部都要注意 this 的绑定。

 

posted @   JackieDYH  阅读(9)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示