前言
后面曾经对防抖和节流有了介绍,这篇次要看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): 须要提早的毫秒数。
// 代码1function 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.jsfunction 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中取出maxWaitif (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中取出maxWaitif (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 // 立刻执行 funcdebounced.pending = pending // 查看以后是否在计时中
演示地址
能够去Github仓库查看演示代码
跟着大佬学系列
次要是日常对每个进阶知识点的摸透,跟着大佬一起去深刻理解JavaScript的语言艺术。
后续会始终更新,心愿各位看官不要悭吝手中的赞。
❤️ 感激各位的反对!!!
❤️ 如果有谬误或者不谨严的中央,请务必给予斧正,非常感激!!!
❤️ 喜爱或者有所启发,欢送 star!!!
参考
- 深刻篇阿里 P6 必会 Lodash 防抖节流函数实现原理
- lodash_debounce.js
- lodash_throttle.js
原文地址
[【跟着大佬学JavaScript】之lodash防抖节流合并
](https://github.com/yihan12/Bl...)