个人学习记录欢迎指正
由于之前在应用节流和防抖时没有注意过主流库的leading,trailing,maxWait等配置。因此在此参考lodash源码重新学习。
1.防抖
上一次调用后延迟X秒后再执行。
1.1 默认版本
参考lodash:默认第一次不执行。
注意:不使用时间戳是因为无法实现延迟
const debounce = function (func, wait) {
let timeId = null;
return function (...args) {
timeId && clearTimeout(timeId);
timeId = setTimeout(() => func(...args), wait);
};
};
1.2 支持取消
支持在延迟时间未结束前取消函数执行。
const debounce = function (func, wait) {
let timeId = null;
const debounced = function (...args) {
timeId && clearTimeout(timeId);
timeId = setTimeout(() => func(...args), wait);
};
debounced.cancel = () => clearTimeout(timeId);
return debounced;
};
1.3 支持绑定this
const debounce = function (func, wait) {
let timeId = null;
return function (...args) {
timeId && clearTimeout(timeId);
timeId = setTimeout(() => func.call(this, ...args), wait);
};
};
1.4 支持函数返回值
支持返回promise成功执行时兑现,取消时拒绝
注意:返回值没有必要,只是个人想法。主流库在使用定时器时不考虑返回值
const debounce = function (func, wait) {
let timeId = null;
let lastReject = () => {};
return async function (...args) {
return new Promise((resolve, reject) => {
if (timeId) {
clearTimeout(timeId);
lastReject();
}
lastReject = reject;
timeId = setTimeout(() => {
resolve(func(...args));
}, wait);
});
};
};
1.5 支持首次调用后,立即执行
支持在延迟前立即执行
举例:lodash.debounce(() => {}, 1000, { leading: true })
const debounce = function (func, wait, leading) {
let timeId = null;
let isInvoke = false;
return function (...args) {
timeId && clearTimeout(timeId);
if (leading && !isInvoke) {
isInvoke = true;
func(...args);
} else {
timeId = setTimeout(() => func(...args), wait);
}
};
};
2.节流
X秒内最多调用一次。
2.1 默认版本
参考lodash:默认第一次执行。
注意:Date.now() - now - wait >= 0不规范,但是Date.now是13位数很大,能够表达节流意思即可。Lodash节流是直接调用防抖函数。
const throttle = function (func, wait) {
let now = 0;
return function (...args) {
if (Date.now() - now - wait >= 0) {
func(...args);
now = Date.now();
}
};
};
2.2 支持取消
支持在时间戳未到达前取消函数节流。
const throttle = function (func, wait) {
let now = 0;
const throttled = function (...args) {
if (Date.now() - now - wait >= 0) {
func(...args);
now = Date.now();
}
};
throttled.cancel = () => (now = 0);
return throttled;
};
2.3 支持绑定this
const throttle = function (func, wait) {
let now = 0;
return function(...args) => {
if (Date.now() - now - wait >= 0) {
func.call(this, ...args);
now = Date.now();
}
};
};
2.4 支持函数返回值
支持返回promise成功执行时兑现,取消时拒绝
注意:返回值没有必要,只是个人想法。主流库在使用定时器时不考虑返回值
const throttle = function (func, wait) {
let now = 0;
return async function (...args) {
return new Promise((resolve, reject) => {
if (Date.now() - now - wait >= 0) {
resolve(func(...args));
now = Date.now();
} else {
reject("rejected");
}
});
};
};
2.5 支持时限内多次调用或首次不调用,在时限后执行一次
默认写法不含此功能,但是lodash等主流库的throttle默认是该功能。
举例:lodash.debounce(() => {}, 1000, { leading: true, trailing: true, maxWait: 1000 })
注意:写法比较复杂(要实现lodash防抖的大部分功能),请参考lodash的debounce源码。下面是简单实现
const throttle = function (func, wait, { leading, trailing }) {
let timeId = null;
// 上次调用参数(用于判断在时限内是否多次调用)
let lastArgs = null;
// 上次调用时间
let lastCallTime = 0;
const leadingEdge = function (now, args) {
if (now - lastCallTime - wait >= 0) {
func(...args);
lastCallTime = leading ? Date.now() : 0;
}
};
const trailingEdge = function (args) {
if (timeId) return;
timeId = setTimeout(() => {
// 判断:多次调用才能触发尾部执行(参考lodash)
lastArgs && func(...args);
lastArgs = null;
}, wait);
};
return function (...args) {
const now = Date.now();
// 判断与执行:是否不执行头部边界
!leading && !lastCallTime && (lastCallTime = now);
// 判断与执行:是否是(1)多次调用(2)没有首次调用
lastCallTime && (lastArgs = args);
// 执行:头部边界
leading && leadingEdge(now, args);
// 执行:尾部边界
trailing && trailingEdge(args);
};
};