前言

前面已经对防抖和节流有了介绍,这篇主要看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): 指定在延迟结束后调用。
  • 然后,我们一般防抖函数,需要的参数是:funcwaitimmediate这三个参数,对应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)。这里主要就是取maxWaitwait中的大值。

3.计算仍需等待的时间

return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;

首先判断是否节流(maxing):

  1. 是=>取「剩余等待时间」和「距上次执行 func 的剩余等待时间」中的最小值。
  2. 否=>取剩余等待时间
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防抖节流合并的更多相关文章

  1. 【跟着大佬学JavaScript】之节流

    前言 js的典型的场景 监听页面的scroll事件 拖拽事件 监听鼠标的 mousemove 事件 ... 这些事件会频繁触发会影响性能,如果使用节流,降低频次,保留了用户体验,又提升了执行速度,节省 ...

  2. 【跟着大佬学JavaScript】之数组去重(结果对比)

    前言 数组去重在面试和工作中都是比较容易见到的问题. 这篇文章主要是来测试多个方法,对下面这个数组的去重结果进行分析讨论.如果有不对的地方,还请大家指出. const arr = [ 1, 1, &q ...

  3. 来聊聊JavaScript中的防抖和节流

    目录 JavaScript防抖和节流 问题还原 防抖 什么是防抖 使用场景 节流 什么是节流 使用场景 JavaScript防抖和节流 问题还原 我们先来通过代码把常见的问题还原: <html& ...

  4. JavaScript函数的防抖和节流

    防抖 触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 思路: 每次触发事件时都取消之前的延时调用方法 function debounce(fn) { let tim ...

  5. 怎么学JavaScript?

    作者:小不了链接:https://zhuanlan.zhihu.com/p/23265155来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 鉴于时不时,有同学私信问我( ...

  6. 统一回复《怎么学JavaScript?》

    作者:小不了链接:https://zhuanlan.zhihu.com/p/23265155来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 鉴于时不时,有同学私信问我( ...

  7. 从头开始学JavaScript (十一)——Object类型

    原文:从头开始学JavaScript (十一)--Object类型 一.object类型 一个object就是一系列属性的集合,一个属性包含一个名字(属性名)和一个值(属性值). object对于在应 ...

  8. 从头开始学JavaScript (十二)——Array类型

    原文:从头开始学JavaScript (十二)--Array类型 一.数组的创建 注:ECMAscript数组的每一项都可以保存任何类型的数据 1.1Array构造函数 var colors = ne ...

  9. 从头开始学JavaScript (十)——垃圾收集

    原文:从头开始学JavaScript (十)--垃圾收集 一.垃圾收集 1.1javascript垃圾收集机制: 自动垃圾收集,执行环境会负责管理代码执行过程中的使用的内存.而在C和C++之类的语言中 ...

随机推荐

  1. 算法篇(1) KMP JS实现最优查找子串

    var strStr = function (haystack, needle) { let i=0, j = 0; let length = haystack.length; let next = ...

  2. Vue3 setup详解

    setup执行的时机 在beforeCreate之前执行(一次),此时组件对象还没创建: this是undefined,不能通过this来访问data/computed/methods/props: ...

  3. zookeeper篇-zookeeper客户端和服务端的基础命令

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 前提:我把zookeepee安装在了服务器/usr/local/java ...

  4. 鸭的NOI ONLINE杂刷

    好耶!洛谷账号橙了! 水题 [NOI Online #2 入门组] 未了 这就是一道贪心+二分查找,思路很好想 除法有精度问题,建议不使用除法 code [NOI Online #3 提高组] 水壶 ...

  5. QY-19 GNSS位移监测站 地质灾害在线监测-实时预警

    概述 GNSS的全称是全球导航卫星系统(Global Navigation Satellite System),它是泛指所有的卫星导航系统,包括全球的.区域的和增强的,如美国的GPS.俄罗斯的Glon ...

  6. Nginx代理websocket为什么要这样做?

    Nginx反向代理websocket 示例: http { map $http_upgrade $connection_upgrade { default upgrade; '' close; } s ...

  7. 初始C语言作业一

    1.下面哪个不是C语言内置的数据类型:( ) A.char B.double C.struct Stu D.short 解析: C语言中内置类型包括 char //字符数据类型 short //短整型 ...

  8. 图文详解 HDFS 的工作机制及其原理

    大家好,我是大D. 今天开始给大家分享关于大数据入门技术栈--Hadoop的学习内容. 初识 Hadoop 为了解决大数据中海量数据的存储与计算问题,Hadoop 提供了一套分布式系统基础架构,核心内 ...

  9. 强制20天加班开发app后被集体解雇,象寻技术负责人公众号发文怒斥前领导

    5月16日下午三点,象寻官方公众号发了一篇<祝象寻早日倒闭的文章>文章,文章配一个竖中指的手势.如此劲爆的文章瞬间引爆了微信朋友圈,大家纷纷分享给好友和微信群,阅读量也达到了十万+. 当时 ...

  10. 好客租房16-jsx中的列表渲染

    如果要渲染一组数组 应该使用数组的map方法 注意:渲染列表时候添加key属性 key属性的值要保持唯一 原则:map()遍历谁 就给谁添加key属性 尽量避免索引号作为key //导入react i ...