【跟着大佬学JavaScript】之lodash防抖节流合并
前言
前面已经对防抖和节流有了介绍,这篇主要看lodash是如何将防抖和节流合并成一个函数的。
初衷是深入lodash,学习它内部的好代码并应用,同时也加深节流防抖的理解。这里会先从防抖开始一步步往后,由简入繁,直到最后实现整个函数。
这里纯粹自己的理解,以及看了很多篇优质文章,希望能加深对节流防抖的理解,如果有不同意见或者看法,欢迎大家评论。
原理
前面虽然已经介绍过防抖和节流原理,这里为了加深印象,再搬过来。
防抖的原理:在wait时间内,持续触发某个事件。第一种情况:如果某个事件触发wait秒内又触发了该事件,就应该以新的事件wait等待时间为准,wait秒后再执行此事件;第二种情况:如果某个事件触发wait秒后,未再触发该事件,则在wait秒后直接执行该事件。
通俗点说:定义wait=3000,持续点击按钮,前后点击间隔都在3秒内,则在最后一次点击按钮后,等待3秒再执行func方法。如果点击完按钮,3秒后未再次点击按钮,则3秒后直接执行func方法。
节流的原理:持续触发某事件,每隔一段时间,只执行一次。
通俗点说,3 秒内多次调用函数,但是在 3 秒间隔内只执行一次,第一次执行后 3 秒 无视后面所有的函数调用请求,也不会延长时间间隔。3 秒间隔结束后则开始执行新的函数调用请求,然后在这新的 3 秒内依旧无视后面所有的函数调用请求,以此类推。
简单来说:每隔单位时间( 3 秒),只执行一次。
代码分析
一、引入代码部分
首先看源码最前方的引入。
import isObject from './isObject.js'
import root from './.internal/root.js'
isObject方法,直接拿出来,
function isObject(value) {
const type = typeof value;
return value != null && (type === "object" || type === "function");
}
root的引入主要是window。为了引出window.requestAnimationFrame。
二、requestAnimationFrame代码
window.requestAnimationFrame()告诉浏览器希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画,差不多 16ms 执行一次。
lodash这里使用requestAnimationFrame,主要是用户使用debounce函数未设置wait的情况下使用requestAnimationFrame。
const useRAF = (!wait && wait !== 0 && typeof window.requestAnimationFrame === 'function')
function startTimer(pendingFunc, wait) {
if (useRAF) {
window.cancelAnimationFrame(timerId)
return window.requestAnimationFrame(pendingFunc)
}
return setTimeout(pendingFunc, wait)
}
function cancelTimer(id) {
if (useRAF) {
return window.cancelAnimationFrame(id)
}
clearTimeout(id)
}
由代码const useRAF = (!wait && wait !== 0 && typeof window.requestAnimationFrame === 'function')不难看出,函数未传入wait并且window.cancelAnimationFrame函数存在这两种情况下操作window.requestAnimationFrame
三、由简入繁输出防抖函数
首先,我们来看下lodash debounce API
这部分参数内容就直接摘抄在下方:- func (Function): 要防抖动的函数。
- [wait=0] (number): 需要延迟的毫秒数。
- [options=] (Object): 选项对象。
- [options.leading=false] (boolean): 指定在延迟开始前调用。
- [options.maxWait] (number): 设置 func 允许被延迟的最大值。
- [options.trailing=true] (boolean): 指定在延迟结束后调用。
然后,我们一般防抖函数,需要的参数是:
func、wait、immediate这三个参数,对应lodash,我们需要拿出这四个部分:- func (Function): 要防抖动的函数。
- [wait=0] (number): 需要延迟的毫秒数。
- [options=] (Object): 选项对象。
- [options.leading=false] (boolean): 指定在延迟开始前调用。
接着,按照这个形式,先写出最简防抖方法。也就是这两部分参数的代码
- func (Function): 要防抖动的函数。
- [wait=0] (number): 需要延迟的毫秒数。
// 代码1
function debounce(func, wait) {
let timerId, // setTimeout 生成的定时器句柄
lastThis, // 保存上一次 this
lastArgs, // 保存上一次执行 debounced 的 arguments
result; // 函数 func 执行后的返回值,多次触发但未满足执行 func 条件时,返回 result
wait = +wait || 0; // 等待时间
// 没传 wait 时调用 window.requestAnimationFrame()
const useRAF =
!wait &&
wait !== 0 &&
typeof window.requestAnimationFrame === "function";
// 取消debounce
function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId);
}
lastArgs = lastThis = timerId = result = undefined;
}
// 开启定时器
// 1.未传wait时使用requestAnimationFrame
// 2.直接使用定时器
function startTimer(pendingFunc, wait) {
if (useRAF) {
window.cancelAnimationFrame(timerId);
return window.requestAnimationFrame(pendingFunc);
}
return setTimeout(pendingFunc, wait);
}
// 定时器回调函数,表示定时结束后的操作
function timerExpired(wait) {
const time = Date.now();
timerId = startTimer(invokeFunc, wait);
}
// 取消定时器
function cancelTimer(id) {
if (useRAF) {
return window.cancelAnimationFrame(id);
}
clearTimeout(id);
timerId = undefined;
}
// 执行函数,并将原函数的返回值result输出
function invokeFunc() {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined; // 清空当前函数指向的this,argumnents
result = func.apply(thisArg, args); // 绑定当前函数指向的this,argumnents
return result;
}
const debounced = function (...args) {
const time = Date.now(); // 获取当前时间
lastArgs = args;
lastThis = this;
if (timerId) {
cancelTimer(timerId);
}
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait);
}
};
debounced.cancel = cancel;
return debounced;
}
看上述代码:
1. 多了未传wait情况,使用`window.requestAnimationFrame`。
2. 将定时器,绑定this,arguments、result和取消定时器等分函数拿了出来。
- 再者,将options的leading加上。也就是immediate立即执行,组成完整的防抖函数。引入参数是下面这部分
- func (Function): 要防抖动的函数。
- [wait=0] (number): 需要延迟的毫秒数。
- [options=] (Object): 选项对象。
- [options.leading=false] (boolean): 指定在延迟开始前调用。
// 代码二
function debounce(func, wait, options) {
let timerId, // setTimeout 生成的定时器句柄
lastThis, // 保存上一次 this
lastArgs, // 保存上一次执行 debounced 的 arguments
result, // 函数 func 执行后的返回值,多次触发但未满足执行 func 条件时,返回 result
lastCallTime; // 上一次调用 debounce 的时间
let leading = false; // 判断是否立即执行,默认false
wait = +wait || 0;
// 从options中获取是否立即执行
if (isObject(options)) {
leading = !!options.leading;
}
// 没传 wait 时调用 window.requestAnimationFrame()
const useRAF =
!wait &&
wait !== 0 &&
typeof window.requestAnimationFrame === "function";
// 取消debounce
function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId);
}
lastArgs = lastThis = timerId = result = lastCallTime = undefined;
}
// 开启定时器
function startTimer(pendingFunc, wait) {
if (useRAF) {
window.cancelAnimationFrame(timerId);
return window.requestAnimationFrame(pendingFunc);
}
return setTimeout(pendingFunc, wait);
}
// 定时器回调函数,表示定时结束后的操作
function timerExpired(wait) {
const time = Date.now();
// 1、是否需要执行
// 执行事件结束后的那次回调,否则重启定时器
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// 2、否则 计算剩余等待时间,重启定时器,保证下一次时延的末尾触发
timerId = startTimer(timerExpired, wait);
}
// 这里时触发后仍调用函数
function trailingEdge(time) {
timerId = undefined;
// 只有当我们有 `lastArgs` 时才调用,这意味着`func'已经被调用过一次。
if (lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
// 取消定时器
function cancelTimer(id) {
if (useRAF) {
return window.cancelAnimationFrame(id);
}
clearTimeout(id);
}
function invokeFunc(time) {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined; // 清空当前函数指向的this,argumnents
result = func.apply(thisArg, args); // 绑定当前函数指向的this,argumnents
return result;
}
// 判断此时是否立即执行 func 函数
// lastCallTime === undefined 第一次调用时
// timeSinceLastCall >= wait 超过超时时间 wait,处理事件结束后的那次回调
// timeSinceLastCall < 0 当前时间 - 上次调用时间小于 0,即更改了系统时间
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime;
return (
lastCallTime === undefined ||
timeSinceLastCall >= wait ||
timeSinceLastCall < 0
);
}
// 立即执行函数
function leadingEdge(time) {
// 1、开启定时器,为了事件结束后的那次回调
timerId = startTimer(timerExpired, wait);
// 1、如果配置了 leading 执行传入函数 func
// leading 来源自 !!options.leading
return leading ? invokeFunc(time) : result;
}
const debounced = function (...args) {
const time = Date.now(); // 获取当前时间
const isInvoking = shouldInvoke(time); // 判断此时是否立即执行 func 函数
lastArgs = args;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
// 判断是否立即执行
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
}
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait);
}
return result;
};
debounced.cancel = cancel;
return debounced;
}
上述代码:
1. 增加trailingEdge、trailingEdge以及invokeFunc函数
2. options目前只支持传入leading参数,也就是immediate。
- 再往后,我们将options中的trailing加上,也就是这四部分
- func (Function): 要防抖动的函数。
- [wait=0] (number): 需要延迟的毫秒数。
- [options=] (Object): 选项对象。
- [options.leading=false] (boolean): 指定在延迟开始前调用。
- [options.trailing=true] (boolean): 指定在延迟结束后调用。
function debounce(func, wait, options) {
let timerId, // setTimeout 生成的定时器句柄
lastThis, // 保存上一次 this
lastArgs, // 保存上一次执行 debounced 的 arguments
result, // 函数 func 执行后的返回值,多次触发但未满足执行 func 条件时,返回 result
lastCallTime; // 上一次调用 debounce 的时间
let leading = false; // 判断是否立即执行,默认false
let trailing = true; // 是否响应事件结束后的那次回调,即最后一次触发,false 时忽略,默认为true
wait = +wait || 0;
// 从options中获取是否立即执行
if (isObject(options)) {
leading = !!options.leading;
trailing = "trailing" in options ? !!options.trailing : trailing;
}
// 没传 wait 时调用 window.requestAnimationFrame()
const useRAF =
!wait &&
wait !== 0 &&
typeof window.requestAnimationFrame === "function";
// 取消debounce
function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId);
}
lastArgs = lastThis = timerId = result = lastCallTime = undefined;
}
// 开启定时器
function startTimer(pendingFunc, wait) {
if (useRAF) {
window.cancelAnimationFrame(timerId);
return window.requestAnimationFrame(pendingFunc);
}
return setTimeout(pendingFunc, wait);
}
// 定时器回调函数,表示定时结束后的操作
function timerExpired(wait) {
const time = Date.now();
// 1、是否需要执行
// 执行事件结束后的那次回调,否则重启定时器
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// 2、否则 计算剩余等待时间,重启定时器,保证下一次时延的末尾触发
timerId = startTimer(timerExpired, remainingWait(time));
}
function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime;
const timeWaiting = wait - timeSinceLastCall;
return timeWaiting;
}
// 这里时触发后仍调用函数
function trailingEdge(time) {
timerId = undefined;
// 这意味着`func'已经被调用过一次。
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
// 取消定时器
function cancelTimer(id) {
if (useRAF) {
return window.cancelAnimationFrame(id);
}
clearTimeout(id);
}
function invokeFunc(time) {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined; // 清空当前函数指向的this,argumnents
result = func.apply(thisArg, args); // 绑定当前函数指向的this,argumnents
return result;
}
// 判断此时是否立即执行 func 函数
// lastCallTime === undefined 第一次调用时
// timeSinceLastCall >= wait 超过超时时间 wait,处理事件结束后的那次回调
// timeSinceLastCall < 0 当前时间 - 上次调用时间小于 0,即更改了系统时间
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime;
return (
lastCallTime === undefined ||
timeSinceLastCall >= wait ||
timeSinceLastCall < 0
);
}
// 立即执行函数
function leadingEdge(time) {
// 1、开启定时器,为了事件结束后的那次回调
timerId = startTimer(timerExpired, wait);
// 1、如果配置了 leading 执行传入函数 func
// leading 来源自 !!options.leading
return leading ? invokeFunc(time) : result;
}
const debounced = function (...args) {
const time = Date.now(); // 获取当前时间
const isInvoking = shouldInvoke(time); // 判断此时是否立即执行 func 函数
lastArgs = args;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
// 判断是否立即执行
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
}
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait);
}
return result;
};
debounced.cancel = cancel;
return debounced;
}
上述代码:
1.leading和trailing不能同时为false。
其实可以在代码中加上判断同时为false时,默认wait=0,直接执行window.requestAnimationFrame部分,而不是定时器。
- 最后结合maxWait,也就是将防抖和节流合并的关键。
- func (Function): 要防抖动的函数。
- [wait=0] (number): 需要延迟的毫秒数。
- [options=] (Object): 选项对象。
- [options.leading=false] (boolean): 指定在延迟开始前调用。
- [options.maxWait] (number): 设置 func 允许被延迟的最大值。
- [options.trailing=true] (boolean): 指定在延迟结束后调用。
首先,我们可以先来看lodash throttle部分源码:
import debounce from './debounce.js'
import isObject from './isObject.js
function throttle(func, wait, options) {
let leading = true
let trailing = true
if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading
trailing = 'trailing' in options ? !!options.trailing : trailing
}
return debounce(func, wait, {
leading,
trailing,
'maxWait': wait
})
}
export default throttle
其实就是将wait传入了debounce函数的option.maxWait中。所以最后,我们只需要将之前的代码加上maxWait参数部分。
function debounce(func, wait, options) {
let timerId, // setTimeout 生成的定时器句柄
lastThis, // 保存上一次 this
lastArgs, // 保存上一次执行 debounced 的 arguments
result, // 函数 func 执行后的返回值,多次触发但未满足执行 func 条件时,返回 result
lastCallTime,
maxWait; // 上一次调用 debounce 的时间
let leading = false; // 判断是否立即执行,默认false
let trailing = true; // 是否响应事件结束后的那次回调,即最后一次触发,false 时忽略,默认为true
/**
* 节流部分参数
**/
let lastInvokeTime = 0; // 上一次执行 func 的时间,配合 maxWait 多用于节流相关
let maxing = false; // 是否有最大等待时间,配合 maxWait 多用于节流相关
wait = +wait || 0;
// 从options中获取是否立即执行
if (isObject(options)) {
leading = !!options.leading;
trailing = "trailing" in options ? !!options.trailing : trailing;
/**
* 节流部分参数
**/
maxing = "maxWait" in options; // options 中是否有 maxWait 属性,节流函数预留
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait; // maxWait 为设置的 maxWait 和 wait 中最大的
// 如果 maxWait < wait,那 maxWait 就没有意义了
}
// 没传 wait 时调用 window.requestAnimationFrame()
const useRAF = !wait && wait !== 0 && typeof window.requestAnimationFrame === "function";
// 取消debounce
function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId);
}
lastInvokeTime = 0;
leading = false;
maxing = false;
trailing = true;
lastArgs = lastThis = timerId = result = lastCallTime = maxWait = undefined;
}
// 开启定时器
function startTimer(pendingFunc, wait) {
if (useRAF) {
window.cancelAnimationFrame(timerId);
return window.requestAnimationFrame(pendingFunc);
}
return setTimeout(pendingFunc, wait);
}
// 定时器回调函数,表示定时结束后的操作
function timerExpired(wait) {
const time = Date.now();
// 1、是否需要执行
// 执行事件结束后的那次回调,否则重启定时器
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// 2、否则 计算剩余等待时间,重启定时器,保证下一次时延的末尾触发
timerId = startTimer(timerExpired, remainingWait(time));
}
// 计算仍需等待的时间
function remainingWait(time) {
// 当前时间距离上一次调用 debounce 的时间差
const timeSinceLastCall = time - lastCallTime;
// 当前时间距离上一次执行 func 的时间差
const timeSinceLastInvoke = time - lastInvokeTime;
// 剩余等待时间
const timeWaiting = wait - timeSinceLastCall;
// 是否设置了最大等待时间
// 是(节流):返回「剩余等待时间」和「距上次执行 func 的剩余等待时间」中的最小值
// 否:返回剩余等待时间
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
// 这里时触发后仍调用函数
function trailingEdge(time) {
timerId = undefined;
// 这意味着`func'已经被调用过一次。
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
// 取消定时器
function cancelTimer(id) {
if (useRAF) {
return window.cancelAnimationFrame(id);
}
clearTimeout(id);
}
function invokeFunc(time) {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined; // 清空当前函数指向的this,argumnents
lastInvokeTime = time;
result = func.apply(thisArg, args); // 绑定当前函数指向的this,argumnents
return result;
}
// 判断此时是否立即执行 func 函数
// lastCallTime === undefined 第一次调用时
// timeSinceLastCall >= wait 超过超时时间 wait,处理事件结束后的那次回调
// timeSinceLastCall < 0 当前时间 - 上次调用时间小于 0,即更改了系统时间
// maxing && timeSinceLastInvoke >= maxWait 超过最大等待时间
function shouldInvoke(time) {
// 当前时间距离上一次调用 debounce 的时间差
const timeSinceLastCall = time - lastCallTime;
// 当前时间距离上一次执行 func 的时间差
const timeSinceLastInvoke = time - lastInvokeTime;
// 上述 4 种情况返回 true
return (
lastCallTime === undefined ||
timeSinceLastCall >= wait ||
timeSinceLastCall < 0 ||
(maxing && timeSinceLastInvoke >= maxWait)
);
}
// 立即执行函数
function leadingEdge(time) {
// 1、设置上一次执行 func 的时间
lastInvokeTime = time;
// 2、开启定时器,为了事件结束后的那次回调
timerId = startTimer(timerExpired, wait);
// 3、如果配置了 leading 执行传入函数 func
// leading 来源自 !!options.leading
return leading ? invokeFunc(time) : result;
}
const debounced = function (...args) {
const time = Date.now(); // 获取当前时间
const isInvoking = shouldInvoke(time); // 判断此时是否立即执行 func 函数
lastArgs = args;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
// 判断是否立即执行
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
// 如果设置了最大等待时间,则立即执行 func
// 1、开启定时器,到时间后触发 trailingEdge 这个函数。
// 2、执行 func,并返回结果
if (maxing) {
// 循环定时器中处理调用
timerId = startTimer(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait);
}
return result;
};
debounced.cancel = cancel;
return debounced;
}
上述代码:
尽管代码有点长,但是实际上只是增加了maxWait。
下面我们分析下maxWait新增的那部分代码。
分析maxWait新增部分
// 1.定义变量
let maxWait; // 上一次调用 debounce 的时间
let lastInvokeTime = 0; // 上一次执行 func 的时间,配合 maxWait 多用于节流相关
let maxing = false; // 是否有最大等待时间,配合 maxWait 多用于节流相关
// 2.从options中取出maxWait
if (isObject(options)) {
maxing = "maxWait" in options; // options 中是否有 maxWait 属性,节流函数预留
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait; // maxWait 为设置的 maxWait 和 wait 中最大的
// 如果 maxWait < wait,那 maxWait 就没有意义了
}
// 3.计算仍需等待的时间
function remainingWait(time) {
// 当前时间距离上一次调用 debounce 的时间差
const timeSinceLastCall = time - lastCallTime;
// 当前时间距离上一次执行 func 的时间差
const timeSinceLastInvoke = time - lastInvokeTime;
// 剩余等待时间
const timeWaiting = wait - timeSinceLastCall;
// 是否设置了最大等待时间
// 是(节流):返回「剩余等待时间」和「距上次执行 func 的剩余等待时间」中的最小值
// 否:返回剩余等待时间
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
// 4.判断是否立即执行
function shouldInvoke(time) {
// 当前时间距离上一次调用 debounce 的时间差
const timeSinceLastCall = time - lastCallTime;
// 当前时间距离上一次执行 func 的时间差
const timeSinceLastInvoke = time - lastInvokeTime;
// 上述 4 种情况返回 true
return (
lastCallTime === undefined ||
timeSinceLastCall >= wait ||
timeSinceLastCall < 0 ||
(maxing && timeSinceLastInvoke >= maxWait)
);
}
// 5.有maxing时,应该如何处理函数
if (isInvoking) {
// 判断是否立即执行
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
// 如果设置了最大等待时间,则立即执行 func
// 1、开启定时器,到时间后触发 trailingEdge 这个函数。
// 2、执行 func,并返回结果
if (maxing) {
// 循环定时器中处理调用
timerId = startTimer(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
1.新增变量就不多说了。
2.从options中取出maxWait:
// 2.从options中取出maxWait
if (isObject(options)) {
maxing = "maxWait" in options; // options 中是否有 maxWait 属性,节流函数预留
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait; // maxWait 为设置的 maxWait 和 wait 中最大的
// 如果 maxWait < wait,那 maxWait 就没有意义了
}
- 1.这里主要是将
maxing,判断是否传了maxWait参数。 - 2.如果未传则
maxWait还是为初始定义的undefined。 - 3.如果传入了
maxWait,则重新赋值Math.max(+options.maxWait || 0, wait)。这里主要就是取maxWait和wait中的大值。
3.计算仍需等待的时间
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
首先判断是否节流(maxing):
- 是=>取「剩余等待时间」和「距上次执行 func 的剩余等待时间」中的最小值。
- 否=>取剩余等待时间
maxWait - (time - lastInvokeTime)
这里是不是就是节流中
// 下次触发 func 剩余时间
const remaining = wait - (now - previous);
4.判断是否立即执行
lodash代码:
maxing && (time - lastInvokeTime) >= maxWait
就往下执行。
这里是不是就是节流中
if (remaining <= 0 || remaining > wait)
就往下执行。
5.有maxing时,应该如何处理函数
lodash代码:如果是节流函数就执行
// 循环定时器中处理调用
timerId = startTimer(timerExpired, wait);
return invokeFunc(lastCallTime);
节流函数中:
timeout = setTimeout(function () {
timeout = null;
previous = options.leading === false ? 0 : getNow(); // 这里是将previous重新赋值当前时间
showResult(context, args);
}, remaining);
总之,lodashmaxWait部分,尽管参数名多,但实际上就是节流函数中,判断剩余时间remaining。不需要等待,就直接立即执行,否则就到剩余时间就执行一次,依次类推。
对外 3 个方法
debounced.cancel = cancel // 取消函数延迟执行
debounced.flush = flush // 立即执行 func
debounced.pending = pending // 检查当前是否在计时中
演示地址
可以去Github仓库查看演示代码
跟着大佬学系列
主要是日常对每个进阶知识点的摸透,跟着大佬一起去深入了解JavaScript的语言艺术。
后续会一直更新,希望各位看官不要吝啬手中的赞。
️ 感谢各位的支持!!!
️ 如果有错误或者不严谨的地方,请务必给予指正,十分感谢!!!
️ 喜欢或者有所启发,欢迎 star!!!
参考
原文地址
【跟着大佬学JavaScript】之lodash防抖节流合并
【跟着大佬学JavaScript】之lodash防抖节流合并的更多相关文章
- 【跟着大佬学JavaScript】之节流
前言 js的典型的场景 监听页面的scroll事件 拖拽事件 监听鼠标的 mousemove 事件 ... 这些事件会频繁触发会影响性能,如果使用节流,降低频次,保留了用户体验,又提升了执行速度,节省 ...
- 【跟着大佬学JavaScript】之数组去重(结果对比)
前言 数组去重在面试和工作中都是比较容易见到的问题. 这篇文章主要是来测试多个方法,对下面这个数组的去重结果进行分析讨论.如果有不对的地方,还请大家指出. const arr = [ 1, 1, &q ...
- 来聊聊JavaScript中的防抖和节流
目录 JavaScript防抖和节流 问题还原 防抖 什么是防抖 使用场景 节流 什么是节流 使用场景 JavaScript防抖和节流 问题还原 我们先来通过代码把常见的问题还原: <html& ...
- JavaScript函数的防抖和节流
防抖 触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 思路: 每次触发事件时都取消之前的延时调用方法 function debounce(fn) { let tim ...
- 怎么学JavaScript?
作者:小不了链接:https://zhuanlan.zhihu.com/p/23265155来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 鉴于时不时,有同学私信问我( ...
- 统一回复《怎么学JavaScript?》
作者:小不了链接:https://zhuanlan.zhihu.com/p/23265155来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 鉴于时不时,有同学私信问我( ...
- 从头开始学JavaScript (十一)——Object类型
原文:从头开始学JavaScript (十一)--Object类型 一.object类型 一个object就是一系列属性的集合,一个属性包含一个名字(属性名)和一个值(属性值). object对于在应 ...
- 从头开始学JavaScript (十二)——Array类型
原文:从头开始学JavaScript (十二)--Array类型 一.数组的创建 注:ECMAscript数组的每一项都可以保存任何类型的数据 1.1Array构造函数 var colors = ne ...
- 从头开始学JavaScript (十)——垃圾收集
原文:从头开始学JavaScript (十)--垃圾收集 一.垃圾收集 1.1javascript垃圾收集机制: 自动垃圾收集,执行环境会负责管理代码执行过程中的使用的内存.而在C和C++之类的语言中 ...
随机推荐
- vue3 数据可视化项目
可视化面板介绍 应对现在数据可视化的趋势,越来越多企业需要在很多场景(营销数据,生产数据,用户数据)下使用,可视化图表来展示体现数据,让数据更加直观,数据特点更加突出. 01-使用技术 完成该项目 ...
- Halo 开源项目学习(四):发布文章与页面
基本介绍 博客最基本的功能就是让作者能够自由发布自己的文章,分享自己观点,记录学习的过程.Halo 为用户提供了发布文章和展示自定义页面的功能,下面我们分析一下这些功能的实现过程. 管理员发布文章 H ...
- XCTF练习题---WEB---robots
XCTF练习题---WEB---robots flag:cyberpeace{6c4b08933075fc620d16d1157ee07a7e} 解题步骤: 1.观察题目,打开场景 2.打开实验场景, ...
- 半导体行业如何保持高效远程办公?因果集群(Causal Clustering)了解一下!
什么是因果集群?因果集群是下一代多站点复制技术.它支持数据中心的分布式系统集群模型.借助于因果集群技术,可以让远程工作团队成员体验到更卓越的性能和更健壮的复制功能,确保您的团队始终以高效状态工作. 因 ...
- java.sql和javax.sql的区别
根据 JDBC 规范,javax.sql 包中的类和接口首先作为 JDBC 2.0 可选包提供.此可选程序包以前与 J2SE1.2 中的 java.sql 程序包是分开的.从 J2SE1.4 开始,这 ...
- Hadoop介绍篇
Hadoop详解 1.前言 对于初次接触Hadoop的小伙伴来说,Hadoop是一个很陌生的东西,尤其是Hadoop与大数据之间的关联,写这篇文章之前,我也有许多关于Hadoop与大数据的疑惑,接下来 ...
- 使用Spring MockMVC对controller做单元测试
1.对单一controller做测试. import org.junit.Before; import org.junit.Test; import org.springframework.beans ...
- C#/VB.NET 创建图片超链接
超链接(Hyperlink)可以看做是一个"热点",它可以从当前Web页定义的位置跳转到其他位置,包括当前页的某个位置.Internet.本地硬盘或局域网上的其他文件,甚至跳转到声 ...
- CMake技术总结
在做算法部署的过程中,我们一般都是用C++开发,主要原因是C++的高效性,而构建维护一个大型C++工程的过程中,如何管理不同子模块之间的依赖.外部依赖库.头文件和源文件如何隔离.编译的时候又该如何相互 ...
- mysql事务管理和mysql用户管理
1.什么是事务? 事务是一条或者是一组语句组成一个单元,这个单元要么全部执行,要么全不执行. 2.事务特性:ACID: A:atomicity原子性:整个事务中的所有操作要么全部成功执行,要么全部失败 ...