/** * @description: 函数节流 * @param {Function} func 回调函数 * @param {Number} wait 等待时间 * @param {{leading:Boolean,trailing:Boolean}} options * @return {Function} * @example const func = throttle(()=>console.log(1),100); */ export default function throttle(func, wait = 100, options) { let timeout, context, args, result; // 上一次执行回调的时间戳 let previous = 0; // 无传入参数时,初始化 options 为空对象 if (!options) options = {}; const later = function () { // 当设置 { leading: false } 时 // 每次触发回调函数后设置 previous 为 0 // 不然为当前时间 previous = options.leading === false ? 0 : Date.now(); // 防止内存泄漏,置为 null 便于后面根据 !timeout 设置新的 timeout timeout = null; // 执行函数 result = func.apply(context, args); if (!timeout) context = args = null; }; // 每次触发事件回调都执行这个函数 // 函数内判断是否执行 func // func 才是我们业务层代码想要执行的函数 function throttled() { // 记录当前时间 const now = Date.now(); // 第一次执行时(此时 previous 为 0,之后为上一次时间戳) // 并且设置了 { leading: false }(表示第一次回调不执行) // 此时设置 previous 为当前值,表示刚执行过,本次就不执行了 if (!previous && options.leading === false) previous = now; // 距离下次触发 func 还需要等待的时间 const remaining = wait - (now - previous); context = this; args = arguments; // 要么是到了间隔时间了,随即触发方法(remaining <= 0) // 要么是没有传入 {leading: false},且第一次触发回调,即立即触发 // 此时 previous 为 0,wait - (now - previous) 也满足 <= 0 // 之后便会把 previous 值迅速置为 now if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); // clearTimeout(timeout) 并不会把 timeout 设为 null // 手动设置,便于后续判断 timeout = null; } // 设置 previous 为当前时间 previous = now; // 执行 func 函数 result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 最后一次需要触发的情况 // 如果已经存在一个定时器,则不会进入该 if 分支 // 如果 {trailing: false},即最后一次不需要触发了,也不会进入这个分支 // 间隔 remaining milliseconds 后触发 later 方法 timeout = setTimeout(later, remaining); } return result; } // 手动取消 throttled.cancel = function () { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; }