前言:游戏之夜的意外转折
本来是美妙而快乐的礼拜五,我本打算拉上三五好友启动PUBG享受周末狂欢的美好夜晚,怎料,老师却让我做个用户搜索功能的任务。我不得不放了兄弟们的鸽子,开始编码。当我写好代码给老师看时,游戏都打开了,老师又和我说,我的代码可以优化,特别是防抖和节流的功能还没有加入,需要再改进。
原码:
后端数据:
{
"users": [
{
"id": "1",
"name": "Rohit"
},
{
"id": "2",
"name": "xkk"
},
{
"id": "3",
"name": "lucky"
}
],
"posts": [
{
"id": "1",
"title": "666",
"content": "Post One Content",
"userId": 1
}
]
}
前端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<label for="unDebounceInput">用户搜索</label>
<input type="text"
id="unDebounceInput"
placeholder="请输入要搜索用户的名字"
>
</div>
<div>
<ul id="users">
</ul>
</div>
<script>
const oUl=document.querySelector("#users")
// 通往后端接口
const oInput=document.getElementById("unDebounceInput")
oInput.addEventListener("keyup",function(){
// console.log("用户输入的内容",this.value) // 当作为事件的处理函数来使用:中的this指向事件发生的对象
let value=this.value.trim(); // 去除前后空格
if(value===""){
oUl.innerHTML="";
return;
}
fetch("http://localhost:3001/users")
.then(res=>res.json())// 二进制 转 JSON
.then(users=>{
// 箭头函数的优雅 + filter 的新功能
const filterUsers=users.filter(
user=> user.name.includes(value)
)
// Array 在ES6 中新增的方法
oUl.innerHTML= filterUsers.map(user=>
`<li> ${user.name}</li>`).join("")
})
})
</script>
</body>
</html>
1.后端:
我使用了JSON Server
来模拟后端的服务,来为前端提供所需要的数据。使用前输入npm run dev
来启动。
2.前端:
实现用户搜索功能,允许用户通过输入框来查找与之匹配的用户名,并将结果展示在一个无序列表(<ul>
)中。
HTML:
这部分比较简单:
- 结构:页面包含一个用于输入搜索关键词的文本框和一个用于显示搜索结果的无序列表。
- 元素关联:通过
for
属性将label
标签与input
元素相关联,提升了可访问性;id="unDebounceInput"
为后续 JavaScript 操作提供了钩子。
JS:
-
DOM 选择器:
- 使用
document.querySelector('#users')
获取结果列表的引用。 - 使用
document.getElementById('unDebounceInput')
获取输入框的引用。
- 使用
-
事件监听:
- 为
input
元素添加了keyup
事件监听器。每当用户释放键盘上的键时,都会触发这个函数。
- 为
-
数据处理:
.trim
的作用为:当用户输入内容并触发keyup
事件后,首先会清理输入值中的前后空白字符,如果没有这步将会导致.filter
中当你不输入值时,将会把数据全显示出来,不符合功能要求。- 如果输入为空,则清空结果列表并直接返回。
- 否则,发起一个 HTTP GET 请求到本地服务器(假设运行了一个 JSON Server),以获取所有用户的列表。
fetch("http://localhost:3001/users")
的作用是向运行在本地(localhost)且监听3001端口的服务器发起一个HTTP GET请求,以获取用户数据。最终会返回一个Promise
.then(res => res.json())
:当第一个Promise被解决时,这个.then
方法会被调用,并将Response对象转换成JSON格式。这也是一个返回Promise的方法,该Promise会在解析完成后得到包含实际数据的JavaScript对象。- 在下一个
.then(users => { ... })
中,你接收到的是之前一步解析出来的用户数组,然后根据用户的输入过滤这个数组,并更新DOM中的<ul>
元素来显示匹配的结果。 - 接收到响应后,利用
Array.prototype.filter()
方法筛选出名字中包含用户输入值的所有用户。
如:
- 最后,使用
Array.prototype.map()
创建新的 HTML 字符串,并将其插入到结果列表中,实现了动态更新 UI 的功能。
不足之处:
虽然实现了功能但是有一个很大的缺点,就是每次键盘的输入,都会引发一次函数的运行以及发送一次请求,这样其实是不太有效率的,那么我们如何改进呢?这就引出来防抖节流的概念了。
防抖节流的概念:
防抖和节流是前端开发中用于优化事件处理的两种常见技术,它们能够有效地减少某些频繁触发事件(如窗口调整大小、滚动、键盘输入等)对性能的影响。下面分别介绍这两种技术:
1. 防抖(Debouncing)
定义: 防抖是指在一定时间内,只允许一个函数执行一次。如果在这个时间段内再次触发了相同的事件,则会重置计时器,并且之前的请求将被取消,直到最后一次触发后的时间间隔结束才执行该函数。
应用场景:
- 用户输入搜索框:防止用户快速打字时发送过多的搜索请求。
- 窗口调整大小:避免在窗口尺寸变化过程中频繁触发布局计算或 AJAX 请求。
- 按钮点击:确保按钮点击不会因为用户的多次快速点击而触发多次提交。
2. 节流(Throttling)
定义: 节流是指在一段时间内限制某个函数的执行频率,即保证这个函数在指定的时间段内最多只能被执行一次。即使在这段时间内发生了多次事件触发,也只会触发一次实际的函数调用。
应用场景:
- 滚动事件:控制页面滚动时加载更多内容或者更新视图的频率。
- 鼠标移动:减少鼠标移动事件处理器的执行次数,提高性能。
- 触摸拖拽:确保触摸拖拽操作不会过于频繁地触发,影响性能。
理解什么是防抖和节流,那么我就可以了思考怎么完善我的代码了
改进代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<label for="unDebounceInput">用户搜索</label>
<input
type="text"
id="unDebounceInput"
placeholder="请输入要搜索的用户名字" >
</div>
<ul id="users">
</ul>
<script>
const oUL = document.querySelector('#users');
// 通往后端接口
const oInput = document.getElementById('unDebounceInput');
// 当作为事件的处理函数来用时,this 指向事件的目标元素
const debounceNameSearch = debounce(handleNameSearch, 500);
oInput.addEventListener('keyup', debounceNameSearch)
function handleNameSearch() {
let value = oInput.value.trim();
if (value === '') {
oUL.innerHTML = '';
return;
}
fetch('http://localhost:3001/users')
.then(res => res.json())
.then(users => {
// console.log(users);
// 箭头函数的优雅 + filter 的新功能
const filterUsers = users.filter(
user => user.name.includes(value)
)
// Array 在es6 中新增的方法
oUL.innerHTML = filterUsers.map(user => `
<li>
${user.name}
</li>
`
).join("")
})
}
// 防抖
// 高阶函数
function debounce(fn, delay) {
let id=null;
return function() {
clearInterval(id);
id=setTimeout(()=>{
fn()
},delay)
}
}
</script>
</body>
</html>
改进之处:
实现了一个带有防抖功能的实时用户搜索框,它通过监听用户的输入并在停止打字后的短暂延迟后执行搜索请求。这不仅提高了用户体验,还减少了不必要的网络请求。
一样的地方我就不多赘述了,我讲讲不一样的地方。
debounce
函数
debounce
是一个高阶函数,返回一个新的函数,这个新函数在调用后会等待指定的毫秒数(delay
),如果在这段时间内再次被调用,则重新计时。- 使用
clearTimeout
来清除之前的定时器,确保只有当用户停止输入一段时间后才会触发实际的函数调用(这里是handleNameSearch
)。 - 如果对定时器不太清楚可以看看我的这篇文章希望对你有帮助: “你会使用 setTimeout 来实现 setInterval 吗?”来自于面试官的灵魂拷问上一世,我站在了梦想的门前 - 掘金
通过这个函数,我们就能实现减少执行次数 只执行连续输入最后一次。
绑定事件监听器
const debounceNameSearch = debounce(handleNameSearch, 500);
oInput.addEventListener('keyup', debounceNameSearch);
debounceNameSearch
是经过防抖处理后的handleNameSearch
函数版本,它会在用户停止输入500毫秒后才执行。- 将
debounceNameSearch
绑定到keyup
事件上,这样每次用户释放一个键时都会检查是否应该发起搜索请求。
成品:
最后大大减少了相应的次数,也实现了功能。
结语:从游戏之夜到代码勇士的转变
在这场编程战役中,我们不仅成功实现了用户搜索功能,还通过引入防抖技术,巧妙地解决了频繁请求带来的性能问题。每一次按键都不再是简单的字符输入,而是触发了一连串精心设计的逻辑,确保只有在用户真正完成输入后,才会发起搜索请求。