手把手教你实现一个防抖函数
前言:防抖函数在日常开发中属于是一个非常非常重要的知识点。通常在一个项目的最开始构建的时候,都会在 utils
文件夹下备上这样一个函数,来为以后做准备。 (tips:utils
在大部分翻译软件内好像都叫跑龙套的,这个翻译不是那么合理。这个单词在这个场景下更像存放工具类的函数的文件夹。通常我们会放一些比如格式化时间,格式化文件大小格式,节流之类的函数。)
这篇文章原意是想紧随在姊妹篇文章节流函数的原理之后发布的。但是那时候自己对闭包、高阶函数的概念不是特别清楚,害怕误导读者,故拖了比较久的才发布这个重要知识点。
注:本文不会讲解防抖的高级写法,只会一步一步带你理清思路,如何拓展功能还需各位看官举一反三。
一. 什么是防抖?使用场景是什么?
首先我们要知道,这里的防抖具体指的是什么?我们假设一个场景,这里就拿我们日常最常用的功能,《搜索〉来举例子。
我们用 v-model 指令绑定这个 <input>
框。然后绑定一个根据用户输入的关键词,去后端数据库检索数据的模拟函数。(这里我们用 console 代替)。
然后我们用 watch
去监视 searchKeyword
的变化,每当用户输入关键词后,我们就向后端发起一次请求。
我们可以非常明显的看到,在这种情况下。我仅仅只是想最后搜索 hanzhenfang
这几个关键词,但是我在输入每一个字符的时候,都会去后端请求一次,数据量小还好,如果数据量过大的话,由于前几次的请求都是毫无意义的,势必会造成性能和资源上的浪费。
4.什么?你说为什么不等最后点击搜索按钮的时候再去搜索? emm... 这个确实是可以。但是突然有一天,产品经理说:“这个搜索框如果有联想功能的话就更好了!我们要赶超百度,赛过谷歌!”你怎么办嘞?目前的情况到不是不行🤔,就是有可能挨后端的一顿毒打(bushi)“...服务器为什么老莫名挂”
ok,现在压力来到了前端这边。接口该调还是得调,但是我希望他在我输入完 hanzhenfang
的时候,然后检测我没有继续往下输入了,再去调后端的接口,然后我再把返回的联想词联系给它展示在这里是不是就可以了呢?
二.理清思路
让我们转化一下思路,只是单纯的这样说你可能不太理解。我们换一个更为简单的场景。
现在页面只有一个简单的按钮,通过点击这个按钮,我们会向后端发起请求。(这种场景我知道有很多别的限制方法🚫,比如在某个时间段内把按钮的 disabled 属性改为 true 等等,我们暂时不讨论这种解决方案。)
现在我们尝试疯狂点击按钮就会疯狂发送请求。
我们现在来修改一下这个函数,我们思考一下🤔,假设我们不借助 debounce 可以实现一个伪防抖的功能吗?答案是百分百可以的。我们先在这个文件下设定一个数字类型的变量叫做 timerID
。稍后我会告诉你为什么是数字类型的。(tips:其实也不是特别需要限制类型 null 这些的也可以)
然后我们设定一个定时器,来使这个 console.log("发请求")
在 1.5s 后执行。
我担心个别读者对《 setTimeout 是有返回值的》这件事不是特别了解。我来穿插讲解一下你可能不知道的知识
其实 setTimeout 会在 setTimeout 执行的时候返回一个大于 0 的正整数。 所以我们这句话其实是在给 timerID 赋值! 并不是将 setTimeout 函数本身赋值给 timerID 这个变量。
⚠️注意: 全文重点是下面这句话:
这里我们需要特别搞清楚 setTimout函数本身执行的时候,是马上赋值的,并不是等到 1.5s 后再赋值的。我希望你多读几句这句话,一定要理解这个概念!
什么意思呢? 我设置了大约在10几年后再执行的一个函数,千万不要觉得 timerID 是会在10年以后才会被赋值。 什么?你不信?来给你演示一下。
为什么要这样设计呢?因为如果这样执行的话,就会给我们一个反悔的机会。还说上面的例子。假设我在 5 年后突然反悔不想执行了。我只需要取消这个 timerID 就可以中途放弃执行。
我们编写一个 cancleSearch
函数,这个函数非常简单。就是一个调用了 clearTimout
这个取消定时器的方法,并且我们把定时器的延时设定为3s。
演示一下:
我的前两次请求已经被我成功阻止了。第三次由于我没点击取消,从而正确的在 3s后帮我执行了 getSearch
函数。
聪明的你可能已经想到了,这个 timerID 就是每一个 setTimeout 的身份证。每当你执行一次setTimout 后,setTimout 所接收的回调函数就会被分配一个唯一 ID,来被放进任务队列。注意!!!一旦任务顺利从任务队列被推进主线程执行后,这个唯一 id 其实作用也就没什么特别大的意义了。
而 clearTimeout
的功能恰好就是清除位于任务队列里指定的 id 所绑定的那个回调函数。
三. 实现一个简单的自我防抖函数
由上面的前备知识,我们就可以实现一个非常简单的自我防抖函数。接下来我梳理一下思路。
当我们每次执行 getSearch
之前,如果当前任务队列里有上一次同样的任务,我们就先清除掉。
然后再去开启一个定时器任务推进任务队列。
至此我们就做到了该函数本身一个简陋的防抖。测试一下,在此之前我们设定一个计算我们点击了多少次按钮的变量,该函数仅仅是为了计数而已。
我们测试一下: