防抖节流,必须必拿下!
一、前言
之前我和大家分享过了图片懒加载,在最后需要解决滚动方法调用频率时,我们留了个后手,那今天就一起来看看什么是防抖和节流,并且探究为什么它们能让面试官屡次提问。
(❁´◡`❁)
二、认识防抖和节流
防抖和节流其实是两种在前端开发中常见的优化技术,主要用于处理高频触发的事件,如窗口的resize、scroll事件,输入框的input事件等,并不是只有图片懒加载需要用到。它们的目的都是为了减少不必要的函数调用,提高程序的性能,缓解服务器压力,毕竟一个请求就是一个并发,而总连接数是有上限的。
它们的基本实现思想是这样的:
- 防抖(Debounce) :防抖的原理是在事件被触发后,在指定的时间内如果事件没有再次被触发,则执行一次函数;如果在这段时间内事件再次被触发,则重新计时。简单来说,就是将多次触发的事件合并为一次执行,常用于需要等待用户操作完全停止后再进行处理的场景,如输入框搜索、窗口大小调整等。
- 节流(Throttle) :节流的原理是在规定的时间间隔内,无论事件触发了多少次,都只执行一次函数。也就是说,它会确保函数以一定的频率执行,而不是无限制地响应事件触发。节流常用于需要频繁但有规律地执行某些操作的场景,如滚动监听、鼠标移动等。
三、实现防抖
现在我们有这样一个任务,做一个类似百度的搜索框,只是简单实现一下它的推荐功能。
当我们想要搜索到需要的内容,一边输字,下面就会一边跳出包含输入内容的可能需要内容。这真的是太香了,用过了都说好。毕竟有时候我们不是很记得想要搜索的内容到底是什么,只能依稀记住一些关键字,或是我们要搜索的内容比较长,当刚输入第一个关键字时,下面就惊讶地跳出了那么一大串想要的内容。 但是,为了缓解服务器压力,并不能过于频繁地跳出下面的内容...
1、创建后端数据
{
"users": [
{
"id": "1",
"name": "张三"
},
{
"id": "2",
"name": "李四"
}
],
"posts": [
{
"id": "7386299931399209010",
"title": "给我一个promise,就跟着你了",
"userID": "1"
},
{
"id": "7385776238788624393",
"title": "时间管理大师:V8说异步就得排异步的队",
"userID": "2"
},
{
"id": "7385132376584945701",
"title": "尝尝这块”曲奇“[cookie]",
"userID": "3"
}
]
}
"scripts": {
"dev": "json-server --watch db.json"
}
"dependencies": {
"json-server": "^1.0.0-beta.1"
}
下载"json-server"依赖来模拟后端接口,快速创建一个模拟的RESTful API服务器。配置文件中加入 "json-server --watch db.json",快速启动服务器。
2、搭建页面
<div>
没有防抖的input
<input
type="text"
id="unDebounce"
placeholder="请输入您要搜索的用户名"
>
</div>
<div>
防抖后的input
<input
type="text"
id="debounce"
placeholder="请输入您要搜索的用户名"
>
</div>
简单搭建两个输入框,分别用来表示使用防抖前后使用后的输入框使用起来的区别。因为我个人看不惯原生的页面,也就加了些样式,稍微看得顺眼。
3、逻辑实现
现在开始给两个输入框加上键盘监听,并给第二个输入框加上防抖功能,看看最后两者的区别。
1、获取Dom节点
const inputa = document.getElementById('unDebounce');
const inputb = document.getElementById('debounce');
获取了两个输入框的引用,getElementById
方法返回页面中 ID 指定元素的引用。
2、定义处理函数
function handleNameSearch(e) {
const value = e.target.value;
fetch('http://localhost:3000/users')
.then(res => res.json())
.then(data => {
const users = data;
const filteredUsers = users.filter(user => {
return user.name.includes(value)
});
console.log(filteredUsers);
});
}
- 上次我们讲过了promise,就会发现其实
fetch
也应该是被promise包裹了,不然无法使用.then
的。 - 当输入框内容改变时会调用
fetch
请求数据,并使用filter
方法过滤出包含当前输入值的用户列表。 - 这里除了使用
includes
方法,也可以用indexOf()
,但是因为用不上下标,所以从性能考虑建议还是使用includes
。
3、定义防抖函数
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
debounce
函数接受一个函数func
和延迟时间delay
。它返回一个新的函数,这个新函数会在最后一次调用后等待delay
时间,然后执行原始函数。如果在这delay
时间内被再次调用,则会重置计时器。- 这里设计一个
timeoutId
作为定时器的id,以便下次准确地清除之前的定时器,来重置时间。 - 为了模块化和代码复用,所以利用闭包将防抖函数和处理函数分开来。这意味着如果你有多个不同的处理函数,只要它们都需要防抖,都可以使用同一个
debounce
函数来包装,而无需为每个处理函数单独实现防抖逻辑。
4、绑定事件监听器i
inputa.addEventListener('keyup', handleNameSearch);fangdao
const debounceNameSearch = debounce(handleNameSearch, 500);
inputb.addEventListener('keyup', debounceNameSearch);
为第一个输入框简单绑定了 handleNameSearch
函数。对于第二个输入框,首先使用 debounce
函数创建了一个防抖版本的 handleNameSearch
函数,然后将其绑定到 keyup
事件上。
最后的效果就不在这展示了,可以拿着代码自己动手试试。(●'◡'●)
四、实现节流
其实在学会理解了什么是防抖后,可以自己认真思考,一个是在最后一次输入后,展示结果;另一个是按照一定的频率,在一定时间内一定要且只有展示一次。 基本和防抖一样,但会更加细致,一起来看看吧。
逻辑实现
那些搭建页面什么的,大差不差,也就不在这里继续堆代码了。并且简单的获取节点,绑定事件按也不重复,照着前面自己写一些,这里直接开始JS中的函数实现吧。
1、定义处理函数
const ajax = (content) => {
console.log(`ajax request ${content}`)
}
定义一个模拟耗时操作的函数 ajax
,在这里只是一个简单的控制台输出,但在真实场景中这可能是发起一个AJAX请求。
2、定义节流函数
const throttle = (func, delay) => {
let last, deferTimer
return (args) => {
let now = +new Date()
if (last && now < last + delay) {
clearTimeout(deferTimer)
deferTimer = setTimeout(() => {
last = now
func(args)
}, delay)
} else {
last = now
func(args)
}
}
}
节流函数 throttle
接受一个函数 func
和一个延迟时间 delay
。内部通过 last
记录上一次执行的时间点,和 deferTimer
定时器ID来管理定时任务。
当输入框事件触发时:
- 计算当前时间
now
。 - 如果距离上次执行时间小于
delay
,则清除之前的定时器并重新设置定时器。 - 如果距离上次执行时间大于等于
delay
,则直接执行函数func
并更新last
时间。
看起来如果if
里面什么都不写,好像也可以实现每隔一定时间触发一次。但是,必须考虑到如果用户在delay
时间内没有按下按键呢。
clearTimeout(deferTimer)
deferTimer = setTimeout(() => {
last = now
func(args)
}, delay)
所以当用户不触发耗时事件(keyup)了,这样做可以让最后一次耗时事件再执行一次呢。只能说保证了用户的使用体验。
五、结语
今天从图片懒加载引出了解决事件触发频率的思想,防抖和节流。了解了它们的基本定义,以及不同的适用场景。接着又一起从实际出发,一步步完成了防抖和节流的功能。 看完希望对你有一点帮助,一起加油!✌️
转载自:https://juejin.cn/post/7386969940941586466