debounce
"Use only that which works, and take it from any place you can find it." —Bruce Lee
本文为 《lodash 源码阅读》 系列文章,后续内容会在 github 中发布,欢迎 star,gitbook 同步更新。
依赖
import isObject from './isObject.js';
import root from './.internal/root.js'; // 当前运行环境
源码注释
/**
* 创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。
* debounced(防抖动)函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。
* 可以提供一个 options(选项) 对象决定如何调用 func 方法,options.leading 与|或 options.trailing 决定延迟前后如何触发。
* func 调用时会传入最后一次提供给 debounced(防抖动)函数 的参数。
* 后续调用的 debounced(防抖动)函数返回是最后一次 func 调用的结果。
*
* 注意: 如果 leading 和 trailing 选项为 true, 则 func 允许 trailing 方式调用的条件为: 在 wait 期间多次调用防抖方法。
*
* 如果 wait 为 0 并且 leading 为 false, func调用将被推迟到下一个点,类似setTimeout为0的超时。
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `debounce` and `throttle`.
*
* @since 0.1.0
* @category Function
* @param {Function} func 要防抖动的函数。
* @param {number} [wait=0]
* 需要延迟的毫秒数,如果为空,间隔则为 `requestAnimationFrame`。
* @param {Object} [options={}] 选项对象。
* @param {boolean} [options.leading=false]
* 指定在延迟开始前调用。
* @param {number} [options.maxWait]
* 设置 func 允许被延迟的最大值。
* @param {boolean} [options.trailing=true]
* 指定在延迟结束后调用。
* @returns {Function} 返回新的 debounced(防抖动)函数。
*/
function debounce(func, wait, options) {
let lastArgs, // 最后一次 debounced 返回函数执行时的参数 args
lastThis, // 最后一次 debounced 返回函数执行时的上下文 this
maxWait, // func 允许被延迟的最大值。
result, // 最后一次执行的结果
timerId, // 定时器id
lastCallTime; // 最后一次 debounced 返回函数执行时的时间
let lastInvokeTime = 0; // 最后一次 func 被执行的时间
let leading = false; // 是否在延迟开始前调用
let maxing = false; // 是否存在 func 允许被延迟的最大值 maxWait
let trailing = true; // 是否在延迟结束后调用
// 通过显示传入 wait=0 绕过 requestAnimationFrame 的使用
const useRAF = !wait && wait !== 0 && typeof root.requestAnimationFrame === 'function';
// func 类型判断
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}
wait = +wait || 0;
// 获取配置属性
if (isObject(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
/**
* 调用 func 的函数
* @param {Number} time 时间戳
*/
function invokeFunc(time) {
// 获取最后一次执行时的 参数 args 及 上下文 this
const args = lastArgs;
const thisArg = lastThis;
// 清空缓存
lastArgs = lastThis = undefined;
// 记录最后一次执行的时间
lastInvokeTime = time;
// 执行 func
result = func.apply(thisArg, args);
// 返回运行结果
return result;
}
/**
* 启动计时器
* @param {Function} pendingFunc 待执行的函数
* @param {Number} wait 延迟时间
*/
function startTimer(pendingFunc, wait) {
// 如果需要使用 requestAnimationFrame,则使用 requestAnimationFrame 进行延时执行
if (useRAF) {
root.cancelAnimationFrame(timerId);
return root.requestAnimationFrame(pendingFunc);
}
// 否则使用 setTimeout 延时 wait ms 执行
return setTimeout(pendingFunc, wait);
}
// 取消定时器
function cancelTimer(id) {
if (useRAF) {
return root.cancelAnimationFrame(id);
}
clearTimeout(id);
}
// 延迟 leading 边缘检测
function leadingEdge(time) {
// 重置最后一次执行时间,达到重置 `maxWait` 功能效果
lastInvokeTime = time;
// 执行定时器,检测 trailing 后置操作是否需要被执行
timerId = startTimer(timerExpired, wait);
// 执行 leading 前置执行
return leading ? invokeFunc(time) : result;
}
// 计算剩余等待时间
function remainingWait(time) {
// 距离 debounced 最后一次被调用的时间
const timeSinceLastCall = time - lastCallTime;
// 距离 func 最后一次执行的时间
const timeSinceLastInvoke = time - lastInvokeTime;
// 剩余等待时间
const timeWaiting = wait - timeSinceLastCall;
// 根据 maxWait 情况,返回剩余等待时间
return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
}
// 判断是否需要执行 func
function shouldInvoke(time) {
// 距离 debounced 最后一次被调用的时间
const timeSinceLastCall = time - lastCallTime;
// 距离 func 最后一次执行的时间
const timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0 || (maxing && timeSinceLastInvoke >= maxWait);
}
function timerExpired() {
const time = Date.now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// 重置定时器
timerId = startTimer(timerExpired, remainingWait(time));
}
// 延迟 trailing 边缘检测
function trailingEdge(time) {
timerId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
// 取消 debounced,重置所有参数
function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
// flush 立即执行 func
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now());
}
// 判断是否在定时器倒计时 pending 过程
function pending() {
return timerId !== undefined;
}
function debounced(...args) {
const time = Date.now();
const isInvoking = shouldInvoke(time);
lastArgs = args;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// 存在 maxing 情况,重置定时器并立即执行
timerId = startTimer(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
debounced.pending = pending;
return debounced;
}
相关链接
Last updated
Was this helpful?