function/throttle.jsjavascript
/**
* 创建节流函数,支持前缘、尾调用以及 cancel/flush 控制。
* @function throttle
* @memberof module:function
* @param {Function} fn 目标函数
* @param {number} [wait=0] 节流窗口毫秒数
* @param {Object} [options] 执行选项
* @param {boolean} [options.leading=true] 是否在节流窗口开始时立即执行
* @param {boolean} [options.trailing=true] 是否在节流窗口结束后执行
* @returns {Function} 带 cancel 和 flush 方法的节流函数
* @throws {TypeError|RangeError} 参数类型或 wait 不合法时抛出
*/
function throttle(fn, wait = 0, options = {}) {
if (typeof fn !== 'function') throw new TypeError('fn must be a function')
if (!Number.isFinite(wait) || wait < 0) throw new RangeError('wait must be a non-negative finite number')
if (options === null || typeof options !== 'object' || Array.isArray(options)) throw new TypeError('options must be an object')
const leading = options.leading ?? true
const trailing = options.trailing ?? true
if (typeof leading !== 'boolean' || typeof trailing !== 'boolean') throw new TypeError('leading and trailing must be booleans')
let timerId = null
let lastInvoke = 0
let lastArgs
let lastThis
let result
const invoke = () => {
const args = lastArgs
const context = lastThis
lastArgs = undefined
lastThis = undefined
lastInvoke = Date.now()
result = fn.apply(context, args)
return result
}
const trailingCall = () => {
timerId = null
if (trailing && lastArgs) invoke()
}
const throttled = function (...args) {
const now = Date.now()
if (lastInvoke === 0 && !leading) lastInvoke = now
const remaining = wait - (now - lastInvoke)
lastArgs = args
lastThis = this
if (remaining <= 0 || remaining > wait) {
if (timerId !== null) clearTimeout(timerId)
timerId = null
invoke()
} else if (timerId === null && trailing) {
timerId = setTimeout(trailingCall, remaining)
}
return result
}
throttled.cancel = () => {
if (timerId !== null) clearTimeout(timerId)
timerId = null
lastInvoke = 0
lastArgs = undefined
lastThis = undefined
}
throttled.flush = () => {
if (timerId === null) return result
clearTimeout(timerId)
trailingCall()
return result
}
return throttled
}
export default throttle