前言:
防抖和节流在js中有很多应用的场景,比如监听滚动条事件,输入字符搜索次数的优化等等。不能只知其然而不知其所以然,所以有必要了解下实现的原理。
区别:
防抖是在一段时间内不能执行某种操作。比如在搜索框这个场景,只有在500ms后没有输入新的字符,才会去调用接口进行搜索。
节流则是有规律的执行某种操作。比如监听滚动条事件这个场景,在一直滚动时,每隔300ms会执行一次滚动条事件的回调。
防抖:
/** * 函数防抖 * @param func 目标函数 * @param delay 延迟时间 * @param immediate 是否立即执行,是指在触发后是否先执行一遍目标函数 */ function debounce (func, delay, immediate) { let timer = null; // 通过闭包来保存timer的值 let _debounce = function () { const context = this; const args = arguments; if (timer) { clearTimeout(timer); } if (immediate) { let callNow = !timer; // 如果timer为null说明是第一次执行 timer = setTimeout(() => { timer = null; }, delay); if (callNow) { func.apply(context, args); } } else { timer = setTimeout(() => { func.apply(context, args); }, delay); } } _debounce.cancel = function () { clearTimeout(timer); timer = null; } return _debounce; }
节流:
/** * 函数节流 * @param func 目标函数 * @param delay 延迟时间 * @param options options.leading:是否在刚触发后立即执行目标函数 * options.trailing:是否在停止触发后还执行一次目标函数 */ function throttle (func, delay, options) { let timer; let previous = 0; // 上一次执行的时间点 options = options || {}; const _throttle = function () { const context = this; const args = arguments; const now = +new Date(); // 如果leading为false,则设置上一次执行的时间点为当前,自然也就不会立马执行目标函数了 if (!previous && options.leading === false) { previous = now; } // 计算距离上次执行的时间点还剩多少时间 const remaining = delay - (now - previous); // 如果剩余时间小于等于0,说明从上次执行到当前已经超过了设定的delay间隔 // 如果剩余时间大于设定的delay,说明当前系统时间被修改过 if (remaining <= 0 || remaining > delay) { if (timer) { clearTimeout(timer); timer = null; } func.apply(context, args); previous = now; } else if (!timer && options.trailing === true) { timer = setTimeout(() => { // 如果leading为false代表下次触发后不需要立马执行目标函数,所以设置为0,在17行才会顺利进入判断 previous = options.leading === false ? 0 : +new Date(); func.apply(context, args); timer = null; }, remaining); // 此处设定为计算距离上次执行的时间点剩余的时间,使用setTimeout保证了即使后续不触发也能再执行一次目标函数 } } _throttle.cancel = function () { clearTimeout(timer); timer = null; previous = 0; } }