一、概念
作用:【防抖】和【节流】的作⽤都是防⽌某个函数被多次调⽤.
区别:假设⽤户⼀直触发某个函数,且每次触发函数的间隔⼩于期望时间 【wait】,【防抖】的情况下只会调⽤【⼀次】,⽽【节流】的情况下会每隔⼀定时间【wait】调⽤函数。
PS:防抖动和节流本质是不⼀样的。防抖动是将多次执⾏变为最后⼀次执⾏,节流是将多次执⾏变成每隔⼀段时间执⾏.
二、防抖 - debounce
PS:防抖函数,返回函数连续调⽤时,空闲时间必须 >= wait,func 才会执⾏.
1. 防抖的简单实现
/** * @param {function} func 传入的回调函数 * @param {number} wait 期望时间 * @return {function} 返回包装的函数 * */ function debounce(func, wait = 50) { let timer = null; // 利用闭包保存变量,否则函数一执行完之后,变量就被初始化 return function (...params) { // 如果已经设定过定时器了就清空上⼀次的定时器 // 否则当前函数被多次触发时,就会存在多个定时器,那么当前函数也会被多次执行 if (timer) { clearTimeout(timer); } // 开始⼀个新的定时器 timer = setTimeout(() => { func.apply(null, params); }, wait); } } 复制代码
以上的实现方式拥有缺陷:
- 如果⽤户调⽤该函数的间隔⼩于 wait 的情况下,上⼀次定时器的时间还未到就被清除了,并不会去执⾏函数
- 防抖函数只能在最后的时刻被调⽤.⼀般的防抖会有 immediate 选项,表示是否⽴即调⽤。
2. 防抖实现(包含 immediate 功能)
/* * @param {function} func 回调函数 * @param {number} wait 期望时间间隔 * @param {boolean} immediate 设置为 ture 时,会⽴即调⽤函数 * @return {function} 返回客户调⽤函数 */ function debounce(func, wait = 50, immediate) { let timer = null; let context, args; // 抽成 later 函数是为了方便多次执行,不需要重复声明 const later = () => setTimeout(() => { // 延迟函数执⾏完毕,清空缓存的定时器序号 timer = null; // 如果已经需要立即执行,那么就不需要在延迟执行一次 if (!immediate) { func.apply(context, args); } // 手动进行垃圾回收 context = args = null; }, wait); return function (...params) { // 1.未创建延迟函数,先创建 if (!timer) { timer = later(); // 1.1需要立即执行一次,就直接执行 if (immediate) { func.apply(this, params); } else { // 1.2不需要立即执行,保存当前 this 和 入参 args,保证在 later 函数执行时回调函数 func 的正确执行 context = this; args = params; } } else { // 2. 延迟函数已执行 // 2.1 先清除原来的并重新设定⼀个 clearTimeout(timer); // 2.2 保证重新计时 timer = later(); } } } 复制代码
三、节流 - throttle
/** * 节流函数,返回函数连续调⽤时,func 执⾏频率限定为【次 / wait】 * * @param {function} func 回调函数 * @param {number} wait 表示时间的间隔 * @return {function} 返回客户调⽤函数 */ function throttle(func, wait = 50, options) { let timer = null; let result; let previous = 0; return function (...args) { // 每次执行函数,先保存当前时间 let now = +new Date(); // 计算剩余时间 let remaining = wait - (now - previous); // remaining <= 0 代表首次执行 // remaining > wait 代表定时器比预期执行时更晚,但也符合执行时机 if (remaining <= 0 || remaining > wait) { // 如果存在定时器就清理掉否则会调⽤⼆次回调 if (timer) { clearTimeout(timer); timeout = null; } // 执行函数时,把当前时间,记录为上一次执行时间 previous = now; result = func.apply(this, args); } else if (!timer) { // 判断是否设置了定时器 // 没有的话就开启⼀个定时器 timer = setTimeout(() => { // 执行函数时,把当前时间,记录为上一次执行时间 // 由于这里是异步的,所以不能直接使用 now,必须要重新获取时间 previous = +new Date(); result = func.apply(this, args); }, remaining); } // 返回结果值 return result; } }