运维开发网

手写酒精防抖功能和油门功能lodash

运维开发网 https://www.qedev.com 2022-05-21 18:28 出处:网络
这篇文章主要介绍了手写Spirit防抖函数underscore和节流函数lodash,接下来将会带你们了解下这两者的区别,以及我们该如何手写实现这两个函数

这篇文章主要介绍了手写Spirit防抖函数underscore和节流函数lodash,接下来将会带你们了解下这两者的区别,以及我们该如何手写实现这两个函数


前言

你肯定听过很多次防抖功能和节流功能,无论是在商务写作还是面试中。但是当你使用它们的时候,你知道它们之间的区别吗?他们是如何实现的?或者干脆调用lodash和下划线之类的第三方库提供的节流和防抖功能?


防抖函数和节流函数的区别

防抖功能:表示触发了一个事件,如果在规定时间内没有触发第二个事件,就执行。换句话说,如果事件被连续触发,那么指定的执行时间就会被连续延迟。


节流功能:意思是无论你在指定时间内触发多少次事件,你都只会执行一次。我举个生活中的例子,很好理解。王者荣耀是一个可能很多人玩的游戏,每个英雄都有自己的本事。我们点击一次后,这个技能就会进入冷却时间。就算我们点的再快,这个技能也只能在冷却时间好之前触发一次(第一次点的时候)。



防抖函数的实现

我会实现防抖功能的四大功能。希望你能按部就班,循序渐进,相信你会有收获的。



基本实现

我们可以考虑如何在JS中实现一个事件,如果我们希望它在指定的时间之后执行。

好了,时间到了

计时器,我的朋友们一定都知道。

触发事件并在一定时间后执行。这可以通过使用定时器来解决。

那么还有一个问题:事件触发后,再触发事件。我们怎样才能让他推迟行刑?

如果在指定时间内再次被触发,我们可以直接删除之前创建的计时器,对吗?

这能解决我们的问题吗?好了,现在把代码写下来,怕大家看不懂。

function debounce(fn, delay) { //定义一个定时器 let timer = null; // 每次触发的时候 清空上一次的定时器 const _debounce = function () { if (timer) clearTimeout(timer); //根据传进来的延时 执行 timer = setTimeout(() =gt; { fn(); }, delay) } return _debounce;}

这个代码还是比较容易的,相信朋友们肯定会理解的。

但是,这段代码仍然存在一些问题。我们姑且称第三方库的防抖功能为下划线。

lt;bodygt; lt;buttongt;取消lt;/buttongt; lt;input type="text"gt; lt;script src="https://cdn.jsdelivr.net/npm/underscore@1.13.1/underscore-umd-min.js"gt;lt;/scriptgt; lt;scriptgt; const btn = document.querySelector("button"); const input = document.querySelector("input"); let count = 0; function test(event) { // 注意这里的this 和 event console.log(`发送${++count}网络请求`, this, event); return "我是返回结果"; } input.oninput = _.debounce(test, 2000); lt;/scriptgt;lt;/bodygt;

我们打开浏览器调试一下,看看输出结果。


可以看到这个and事件的输出没有问题。

让我们再次查看我们的输出。


你会发现这是窗口,事件是未定义的。

这是为什么呢?

这是因为我们写的代码没有绑定这个,也没有接收DOM element的事件。

Fn()此时直接执行这个,直接指向窗口。

function debounce(fn, delay) { let timer = null; //使用剩余参数接收所有的参数 DOM在调用这个函数的时候,我们就能接收到event了 const _debounce = function (...args) { if (timer) clearTimeout(timer); timer = setTimeout(() =gt; { //注意 我们使用apply进行一个换绑,绑到执行这个的DOM元素上 fn.apply(this,args); }, delay) } return _debounce;}

至此,我们的防抖功能基本实现没有问题。

在这里见到你的小伙伴真的很开心。

这种基本的实现对于面试来说已经足够了。接下来,我们还需要实现一些额外的功能。想看可以继续往下看,现在不想看可以留着,以后再看。


立即执行

在一些应用场景中,比如搜索,当你输入第一个字符时,他会想到一串字符。他不会等一会儿再执行,而是会马上执行。接下来我们将实现这个函数。

第一,立即执行这个功能,我们可以交给用户来决定是否使用这个功能。

debounce(fn,delay,immediate=false)

我们将它作为参数传递,默认情况下它是封闭的。

好了,现在来看代码实现。

function debounce(fn, delay, immediate = false) { let timer = null; //代码规范 我们最好不要修改 用户传递进来的参数 //所以我们在下面声明了一个变量 用于控制 let isInvoke = false; const _debounce = function (...args) { if (timer) clearTimeout(timer); //如果immdiate为true //isInvoke取反为true if (immediate amp;amp; !isInvoke) { //会立马执行一次 fn.apply(this, args); //同时将isInvoke设置为true,防止下次触发的时候 又再次触发立即执行 isInvoke = true; } else { //第一次触发结束立即执行后 //isInvoke会限定在定时器中 输入结束后 才会重新刷新isInvoke timer = setTimeout(() =gt; { //剩下的操作在规定时间内 会等待定时器结束 fn.apply(this, args); //同时重新刷新inInvoke isInvoke = false; }, delay) } } return _debounce;}

好吧,这一块比较简单。和别人比,你要懂。有什么不明白的,请在评论区留言。我看到了就来回答。

那么,如果我们开始下一章,并且不希望用户在输入后请求它,该怎么办呢?这时,我们需要一个取消功能。是的,我们接下来要实现取消功能。


取消功能

如何在剩余时间内取消请求?

对,没错!清除空定时器

我们只需要在返回的函数中添加一个静态方法,为用户提供一个取消功能。

让我们看看代码实现。

// 给返回的这个函数添加一个静态方法 用于取消请求 _debounce.cancel = function () { if (timer) clearTimeout(timer); }

是不是很简单?取消功能只需一行代码。

好了,我们还有最后一个功能要实现,就是如果开发者想得到请求后的返回结果,我们现阶段的防抖功能能做到吗?我不这么认为。

所以接下来,让我们实现最后一个函数来获得返回的结果。


返回结果

我们来思考一个问题。结果在哪里?

用户传递一个函数并返回一个新函数给我们。

那么返回的结果必须在用户传递给我们的函数上。

所以关键是要传递用户函数的返回结果。

现在我们有两个计划。

回调函数Promise

我们先来看看回调函数的版本。

// 回调函数版本function debounce(fn, delay, immediate = false, resultCallBack) { let timer = null; let isInvoke = false; let result = null; const _debounce = function (...args) { if (timer) clearTimeout(timer); if (immediate amp;amp; !isInvoke) { //接收结果 result = fn.apply(this, args); resultCallBack(result); isInvoke = true; } else { timer = setTimeout(() =gt; { //接收结果 result = fn.apply(this, args); resultCallBack(result); isInvoke = false; }, delay) } } _debounce.cancel = function () { if (timer) clearTimeout(timer); timer = null; isInvoke = false; } return _debounce;}

实际应用

const _debounce = () =gt; { debounce(test, 1000)().then(res =gt; { console.log(res); }) } input.oninput = _debounce;

回调函数简单吗?先来看看无极版,在实际应用中注意一些坑。

function debounce(fn, delay, immediate = false) { let timer = null; let isInvoke = false; let result = null; const _debounce = function (...args) { //在返回的函数中 直接整体返回一个Promsie对象 //将结果传入 resolve中 return new Promise((resolve, rejected) =gt; { if (timer) clearTimeout(timer); if (immediate amp;amp; !isInvoke) { result = fn.apply(this, args); resolve(result) isInvoke = true; } else { timer = setTimeout(() =gt; { result = fn.apply(this, args); resolve(result); isInvoke = false; }, delay) } }) } _debounce.cancel = function () { if (timer) clearTimeout(timer); timer = null; isInvoke = false; } return _debounce;}

实际呼叫

const _debounce = function(...args){ debounce(test, 1000).apply(this,args).then(res =gt; { console.log(res); }) }; input.oninput = _debounce;

注意,我们对原始函数有另一层封装,因为这是获得预期结果的唯一方法。

同时,这个和事件也不会出错。

很高兴在这里见到朋友们。我相信你的防抖功能不会有问题的。我们将在后面开始解释如何实现节流功能。


节流函数的实现

节流功能也是从几个方面逐步实现的,带领大家一步步解开节流功能的面纱。

基本实现

可以考虑一下如何实现节流功能。

一段时间内,只会触发一次操作,不会触发后续操作。

我们可以得到当前的时间戳来计算。

直接通过代码来说吧。这样更方便。

function throttle(fn, interval) { let lastTime = 0; const _throttle = function () { //首先拿到当前的时间 const nowTime = new Date().getTime(); //传递进来的时间间隔 用当前的时间减去上一次触发的时间 //得到最新的剩余时间 const reamainTime = interval - (nowTime - lastTime); if (reamainTime lt;= 0) { fn(); //如果剩余时间小于0 说明已经达到一个间隔 //并且将现在的时间赋值给lastTime //在时间间隔内 这样无论执行多少次 都只会执行第一次的操作 //因为第一次的lastTime是0 而nowTime是比较大的 //减去之后一定是个负数 所以会执行第一次 //而不会执行后续的操作 lastTime = nowTime; } } return _throttle;}

大家看看我上面的代码,很容易理解。面试的时候写这个部分还可以,但是如果你想更出彩的话,可以多和面试官对话。让我们看看下面的实现。


leading实现

在我们的基本实现中,这个功能其实已经实现了,但是不可控。我们的实现是给用户第一个触发来决定,你可以思考如何实现。

在基本实现中,我们如何实现第一个触发器?

是因为得到了一个很大的时间戳,而lastTime是0吗?

那么,我们能让lastTime也获得当前时间戳吗?这样,nowTime和lastTime相减,不会变成负数吗?

代码实现

// 考虑到 我们后面会有很多功能要实现//所以我们使用选项来进行配置.避免造成更多参数function throttle(fn, interval, option = { leading: true }) { let lastTime = 0; const { leading } = option; const _throttle = function () { const nowTime = new Date().getTime(); //在 leading和lastTime为false的情况下 //就将nowTime赋值给lastTime,这样就不会在第一次就执行操作了 if (!leading amp;amp; !lastTime) lastTime = nowTime; const reamainTime = interval - (nowTime - lastTime); if (reamainTime lt;= 0) { fn(); lastTime = nowTime; } } return _throttle;}

你明白吗?个人觉得比较好理解。如果你不明白,可以在评论区留言,我看到了会给你解答。

接下来,我们来看相反的情况。如果要触发最后一个操作,应该怎么做?


trailing实现

这是一个比较难的部分,会有点难。不懂的话要多看几遍。如果真的不懂,请在评论区留言。

当操作最后一次被触发时,如何才能让它执行?

我提供一个思路,当我们最后一次触发操作的时候,距离间隔结束还有多少时间,加上一个定时器,让他根据剩余时间按时执行。

代码实现

function throttle(fn, interval, option = { leading: true, tralling: false }) { let lastTime = 0; let timer = null; const { leading, tralling } = option; const _throttle = function (...args) { const nowTime = new Date().getTime(); if (!leading amp;amp; !lastTime) lastTime = nowTime; const reamainTime = interval - (nowTime - lastTime); if (reamainTime lt;= 0) { fn.apply(this, args); lastTime = nowTime; if (timer) { clearTimeout(timer) timer = null; } // 如果执行了这一部分 那么后面的tralling就没有必要去执行 // 说明刚好执行到了这一步 后面的最后按下 就不需要 return; } if (tralling amp;amp; !timer) { timer = setTimeout(() =gt; { timer = null; /** ` * 首先 按下第一次的时候 这个定时器已经被加上了 * 每次进来的时候 等待一定时间 定时器会被置空 方便下次使用 * 根据剩余时间 来判断执行 * 如果leading为false lastTime会被设置为0 会在规定的剩余时间到达后 去执行这个函数 而remianTime那个部分就不会被执行 因为remainTime会一直保持在一个正数状态 * 如果leading为true lastTime会被设置为当前的时间 这样在下一次的操作下,remainTime才会发生变化 * */ lastTime = !leading ? 0 : new Date().getTime(); fn.apply(this, args); }, reamainTime) } } return _throttle;}

很难理解吗?让我解释一下。

首先,如果remainTime已经小于0,那么fn会执行它,所以我们不需要执行后续操作,直接返回。

然后,如果剩余时间不小于0,我们将设置计时器。在定时器内部,我们需要先清零timer 空,防止下次再次被触发。

其次,我们需要处理最后一次。

如果我们之前对前导的设置为false,那么我们需要将lastTime设置为0,这样在下一次触发操作中就可以触发前导为false的逻辑语句。

当leading为true时,需要将lastTime设置为当前时间戳,这样remainTime时间就会改变,逻辑会在下一个操作中执行。

每个人都明白了吗?可能有点难懂,但是看了几遍,相信你能看懂!!!

接下来的操作就比较简单了,可以安心吃了。和防抖功能一样,取消功能,返回结果。


取消功能和返回结果

因为这个和防抖功能是一样的,所以我就把代码放在这里了。

function throttle(fn, interval, option = { leading: true, tralling: false, resultCallback }) { let lastTime = 0; let timer = null; let result = null; const { leading, tralling, resultCallback } = option; // 两种结果回调 //和防抖函数是一样的 //1. 通过传递一个回调函数 //2. 通过promise 进行结果回调 const _throttle = function (...args) { return new Promise((resolve, reject) =gt; { const nowTime = new Date().getTime(); if (!leading amp;amp; !lastTime) lastTime = nowTime; const reamainTime = interval - (nowTime - lastTime); if (reamainTime lt;= 0) { result = fn.apply(this, args); resultCallback(result); resolve(result); lastTime = nowTime; if (timer) { clearTimeout(timer) timer = null; } return; } if (tralling amp;amp; !timer) { timer = setTimeout(() =gt; { timer = null; lastTime = !leading ? 0 : new Date().getTime(); result = fn.apply(this, args); resultCallback(result); resolve(result); }, reamainTime) } }) } //取消功能 _throttle.cancel = function () { if (timer) clearTimeout(timer); timer = null; lastTime = 0; } return _throttle;}

你能看看是不是一样的吗?很放松吧?

以上是手写的Spirit防抖功能下划线和油门功能lodash的细节。

0

精彩评论

暂无评论...
验证码 换一张
取 消