手把手教你实现一个防抖函数
前言:防抖函数在日常开发中属于是一个非常非常重要的知识点。通常在一个项目的最开始构建的时候,都会在 utils文件夹下备上这样一个函数,来为以后做准备。 (tips:utils 在大部分翻译软件内好像都叫跑龙套的,这个翻译不是那么合理。这个单词在这个场景下更像存放工具类的函数的文件夹。通常我们会放一些比如格式化时间,格式化文件大小格式,节流之类的函数。)
一. 什么是防抖?使用场景是什么?
1.首先我们要知道,这里的防抖具体指的是什么?我们假设一个场景,这里就拿我们日常最常用的功能,《搜索〉来举例子。 。%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/4cc9536ab93fa2d9adcb73e0d9b9cb00.png%29%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/0ef7a184b865936c33d71b38558221ad.png%29%20然后我们用%20`watch`%20去监视%20`searchKeyword`%20的变化,每当用户输入关键词后,我们就向后端发起一次请求。%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/fea6134f6d6e07281df9b244a878880d.png%293.我们可以非常明显的看到,在这种情况下。我仅仅只是想最后搜索%20`hanzhenfang`%20这几个关键词,但是我在输入每一个字符的时候,都会去后端请求一次,数据量小还好,如果数据量过大的话,由于前几次的请求都是毫无意义的,势必会造成性能和资源上的浪费。![搜索.gif]%28https://img-blog.csdnimg.cn/img_convert/7c3a930f98beb772ca49f7f8f9ee947a.gif%294.**什么?你说为什么不等最后点击搜索按钮的时候再去搜索?**%20emm...%20这个确实是可以。但是突然有一天,产品经理说:“这个搜索框如果有联想功能的话就更好了!我们要赶超百度,赛过谷歌!”你怎么办嘞?目前的情况到不是不行🤔,就是有可能挨后端的一顿毒打(bushi)“...服务器为什么老莫名挂”![联想.gif]%28https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f8e1d41e35f54ef9a97c193d6b4d0b8b~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image? )
5.ok,现在压力来到了前端这边。接口该调还是得调,但是我希望他在我输入完 hanzhenfang 的时候,然后检测我没有继续往下输入了,再去调后端的接口,然后我再把返回的联想词联系给它展示在这里是不是就可以了呢?
二.理清思路
1.让我们转化一下思路,只是单纯的这样说你可能不太理解。我们换一个更为简单的场景。 <img src=“https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0024961182e74cfea8d53fd0219d5451~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image?)
现在页面只有一个简单的按钮,通过点击这个按钮,我们会向后端发起请求。(这种场景我知道有很多别的限制方法🚫,比如在某个时间段内把按钮的 disabled 属性改为 true 等等,我们暂时不讨论这种解决方案。)
2.现在我们尝试疯狂点击按钮就会疯狂发送请求。
3.我们现在来修改一下这个函数,我们思考一下🤔,假设我们不借助 debounce 可以实现一个伪防抖的功能吗?答案是百分百可以的。我们先在这个文件下设定一个数字类型的变量叫做 timerID。稍后我会告诉你为什么是数字类型的。(tips:其实也不是特别需要限制类型 null 这些的也可以)
4.然后我们设定一个定时器,来使这个 console.log("发请求") 在 1.5s 后执行。
5.我担心个别读者对《 setTimeout 是有返回值的》这件事不是特别了解。我来穿插讲解一下你可能不知道的知识。
其实 setTimeout 会在 setTimeout 执行的时候返回一个大于 0 的正整数。 所以我们这句话其实是在给 timerID 赋值! 并不是将 setTimeout 函数本身赋值给 timerID 这个变量。
6.⚠️注意: 全文重点是下面这句话:
这里我们需要特别搞清楚 setTimout函数本身执行的时候,是马上赋值的,并不是等到 1.5s 后再赋值的。我希望你多读几句这句话,一定要理解这个概念!什么意思呢? 我设置了大约在10几年后再执行的一个函数,千万不要觉得 timerID 是会在10年以后才会被赋值。 什么?你不信?来给你演示一下。 
7.为什么要这样设计呢?因为如果这样执行的话,就会给我们一个反悔的机会。还说上面的例子。假设我在 5 年后突然反悔不想执行了。我只需要取消这个 timerID 就可以中途放弃执行。8.我们编写一个 cancleSearch 函数,这个函数非常简单。就是一个调用了 clearTimout 这个取消定时器的方法,并且我们把定时器的延时设定为3s。
演示一下: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dGE1XqSK-1676903606651)(null)] 可以清楚的看到,我的前两次请求已经被我成功阻止了。第三次由于我没点击取消,从而正确的在 3s后帮我执行了 getSearch 函数。9.聪明的你可能已经想到了,这个 timerID 就是每一个 setTimeout 的身份证。每当你执行一次 setTimout 后,setTimout 所接收的回调函数就会被分配一个唯一 ID,来被放进任务队列。注意!!!一旦任务顺利从任务队列被推进主线程执行后,这个唯一 id 其实作用也就没什么特别大的意义了。
10.而 clearTimeout 的功能恰好就是清除位于任务队列里指定的 id 所绑定的那个回调函数。三. 实现一个简单的自我防抖函” style=“margin: auto” />
1.由上面的前备知识,我们就可以实现一个非常简单的自我防抖函数。接下来我梳理一下思路。 
1.我们只有一个函数需要防抖的话其实这样看着还行,但是现在有10个,100函数呢?我难道一个个这样写吗?nonono,程序员都是很懒滴~是不可能写重复的低质量代码的。所以聪明的你可能会想到会写一个生产 自我防抖 函数的函数。没错,引出我们今天的主角 debounce。2.好的,铺垫了这么久,也该敲敲代码了。我们先在 utils 文件夹下创建 debounce.ts 的文件来放我们的防抖函数。顾名思义。 %20![image.png]%28https://img-blog.csdnimg.cn/img_convert/e85c12a5ca9a2b01edb5afa7577a599b.png%294.然后在%20**debounce**%20函数定义一个局部变量%20**timeID**%20来存放我们后面定时器返回的**身份证id**。%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/0fc4f356110c3f17c6825f66e9e79cb7.png%295.**⚠️注意:接下来是本文的第二个重点**。这里我们需要用到**高阶函数**。让我们先看看高阶函数的定义是什么。%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/f403f2b5e321b802567220b94d78dc6d.png%29%20不要怕,它并不是像**数学**和**高等数学**的差距那样!如果你是第一次听到这个名词,你可以这样理解:就像你送别人礼物,你为了好看,你会把这个物品给用精美的包装给包一层。那么我们的%20**防抖**%20函数在这里的作用其实就是帮你把这个函数包装一下的意思,它并不会直接影响这个函数的内部逻辑,就像你的礼物包装一层包装纸🎁后,它本身是没有发生任何变化的。6.所以在这里我们应该返回一个函数来存放我们真正的业务代码。%28为了方便写成了匿名函数,你也可以先在函数内部使用%20**function关键词声明一个带名字的函数**%20最后返回,效果是一样的%29%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/c90eb4f1d1a725091b5b458d00d9f251.png%297.然后直接把我们上一步实现的自我防抖函数内部的逻辑复制过来。%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/6eafc544e85f38dabde578d60563c638.png%298.就是这么简单~%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/b947394ce748488737e970909b7970fb.png%29%20哦,稍等,别忘了把%20`setTimeout`里的%20`console.log%28'发请求'%29`%20替换成我们的参数%20`fn`。%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/66bf32154fba6fc1df7c96b28a93a327.png%299.接下来去%20**app.vue**%20里引入这个函数。%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/70c3caefc8adfd4962e659a09778faea.png%29五.%20闭包和%20**debounce**%20的关 )
1.等等,别着急。我大概能能猜到你会这样使用。 对于现在的场景简单来说,你可以这样理解。闭包相当于在自身的范围内,通过在函数内部引用自己的%20变量**timerID**%20来达成变量%20**timeID**%20在%20**debounce**%20函数执行后并不会被马上销毁的目的。%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/931839ac904dedc0ed02154457df5099.png%296.那么我们正确的写法就是,用一个变量来接收%20`debounce`%20返回的那个函数。并且此时此刻,已经同步生成了一个暂时不会被销毁的%20**timeID**%20来保存我们%20`setTimeout`%20生产的那个%20**id**身份证。(这里可能不是特别好理解,需要读者自行去了解闭包的机制)%20![image.png]%28https://img-blog.csdnimg.cn/img_convert/c8a7dad591f8ead40f0a6644963ba592.png%29%20测试一下:![jieshu.gif]%28https://img-blog.csdnimg.cn/img_convert/c862a704c922d13ce85b215e45bf0b2e.gif%29总结 )
如果读者能够细心品文本篇文章的细节,你可能会自然而然的理解节流的原理是什么。节流相关知识我之前也是通俗易懂的用游戏技能冷却🎮带你去理解原理是什么。有兴趣的读者可以自行查阅~
特别感谢:
@林水溶君@Baoyuan0808
特别感谢:
@林水溶君@Baoyuan0808
最后感谢两位大佬对我写本文提供的思路和技术指导。🎁
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。




有需要的小伙伴,可以点击下方卡片领取,无偿分享
防抖函数在前端开发中很重要,常用于优化性能。文中通过搜索功能举例,解释了防抖的使用场景,即在用户停止输入后延迟执行接口调用。通过设置定时器和利用clearTimeout实现了一个简单的防抖逻辑。此外,文章还介绍了如何创建一个通用的防抖函数(debounce)来避免重复代码,并提到了与节流的区别。
7207

被折叠的 条评论
为什么被折叠?



