前端性能优化篇:防抖与节流性能优化 防抖(Debounce) 何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操
性能优化是在软件开发过程中一个非常重要的环节,尤其是在Web开发领域,因为用户体验很大程度上取决于页面加载速度和交互响应性。而 防抖(Debounce)和节流(Throttle) 是前端开发中常用的两种优化技术,用于减少事件处理函数的执行频率,从而提高应用的性能并减少不必要的计算开销。今天就让我们来实现一下这两种功能吧。
性能优化
防抖(Debounce)
何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操作,前提是最后一次操作之后有一段特定的时间没有新的操作发生。如果在这段时间内又有新的操作发生,则重新计时。
我们可以想象一下,当我们在浏览器输入内容时会有相关词汇出现在下方;这是因为在我们输入的过程中就会频繁地触发输入框的搜索功能,去为我们拿到相关词汇;这种场景下就需要我们对输入框添加一个防抖的功能。
那让我们来模拟一下这个过程;并实现防抖的功能。
初始化后端项目
首先,我们需要初始化一个后端项目来存放数据资源;
npm init -y // 初始化后端
// json-server 是驱动的后端接口 API
npm i json-server // 安装 json-server
// 启动服务器
json-server --watch db.json
JSON Server
json-server
是一个简单的命令行工具,它可以让你快速地创建一个模拟的REST API服务器,这个服务器会从一个JSON文件读取数据,并且提供CRUD操作的端点。它还可以通过一个 JSON 文件或一个目录来启动一个具有 GET 和 POST 请求能力的 API 服务器。
db.json
存放数据资源
{
"users": [
{
"id": "1",
"name": "kunkun"
},
{
"id": "2",
"name": "kunfc"
},
{
"id": "3",
"name": "kfc"
}
],
"post": [
{
"id": "7379599360944177189",
"title": "什么是拷贝?我:Ctrl + C ...",
"userId": "1"
},
{
"id": "7370244444282896410",
"title": "HTML5:从网页到富应用的跨越",
"userId": "2"
},
{
"id": "7378028569689554985",
"title": "吴恩达系列之:Prompt Engineering——提示词工程",
"userId": "3"
},
{
"id": "7377901375001329727",
"title": "从泡茶艺术到编程智慧:模板方法模式",
"userId": "4"
}
]
}
Restful
RESTful API 是一种设计风格,它认为一切皆是资源,并且每个资源都应该有一个唯一的 URL,并且应该使用 HTTP 方法(GET, POST, PUT, DELETE 等)来操作这些资源。
如下:
- 获取所有用户:
GET http://localhost:3000/users
- 获取指定用户:
GET http://localhost:3000/users/:id
- 获取所有文章:
GET http://localhost:3000/posts
- 获取指定文章:
GET http://localhost:3000/posts/:id
- 新建文章:
POST http://localhost:3000/posts
前端搜索页面
接下来,我们就要去设计一个简单的搜索页面来实现 “猜你想要查找” 的功能;
<!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>
没有防抖的input
<input
type="text"
id="unDebounce"
placeholder="请输入您要搜索的用户名"
>
</div>
<div>
防抖后的input
<input
type="text"
id="debounce"
placeholder="请输入您要搜索的用户名"
>
</div>
<script>
// 不加防抖的
const inputa = document.getElementById("unDebounce");
const inputb = document.getElementById("debounce");
/**
* 处理用户名称搜索事件。
* 该函数在用户在输入框中输入名称时被调用,通过fetch获取所有用户数据,并根据输入值过滤用户。
* @param {Event} e - 输入事件对象
*/
function handleNameSearch(e) {
// 获取输入框的值
const value = e.target.value;
// 使用fetch获取'http://localhost:3000/users'的内容,并返回一个Promise
fetch('http://localhost:3000/users')
// 当Promise完成并返回响应对象时,调用此函数,解析响应的JSON数据并返回一个Promise
.then(res => res.json())
// 当 Promise 完成并返回用户数据时,调用此函数
.then(data => {
// 将用户数据赋值给一个名为users的常量
const users = data;
// 使用filter方法创建一个新的数组,只包含符合过滤条件的用户
// filter方法的回调函数返回一个真值,将元素保留在结果数组中,否则返回一个假值
const filterUsers = users.filter(user => {
// 使用includes方法检查用户名称是否包含搜索字符串
return user.name.includes(value)
// 使用字符串的indexOf方法检查用户名称是否包含搜索字符串
// return user.name.indexOf(value)!== -1
})
// 打印过滤后的用户数组到控制台
console.log(filterUsers);
})
}
inputa.addEventListener('keyup',handleNameSearch)
inputb.addEventListener('keyup', debounceNameSearch)
</script>
</body>
</html>
在上面的代码中,handleNameSearch
函数通过 fetch
发送 AJAX 请求从服务器获取所有用户数据,并使用 res.json()
方法解析 JSON 数据,将拿到的数据每一项用数组上es6的新方法 filter
去过滤数据,如果后端数据中包含输入框中的值,就将其保留在返回的新数组中;要注意 filter 方法的回调函数返回一个真值,将元素保留在结果数组中,否则返回一个假值,所以回调函数必须要返回的是布尔型的值。
这样我们就实现了一个 “猜你想要查找” 的功能;但此时没有添加防抖,每次输入值都会频繁的触发函数handleNameSearch
的调用。
事件监听与 AJAX 请求
当用户在输入框中键入文本时,通常会触发 keyup
事件。如果不加以控制,每次 keyup
事件都会触发 AJAX 请求来获取建议列表,这可能导致大量的无谓请求。为了减轻服务器压力并提高用户体验,我们可以使用防抖来限制请求的频率。
实现防抖
在用户频繁触发某个事件时(如键盘输入或窗口大小改变),防抖可以确保只执行最后一次操作,前提是最后一次操作之后有一段特定的时间没有新的操作发生。那我们要如何实现防抖呢?
// 闭包功能函数
function debounce(func, delay) {
// 返回值必须得是函数 keyup 事件处理函数
// let id;
return function(args){
clearTimeout(func.id)
// 函数是对象,id 挂在func上 func 是闭包中的自由变量
func.id = setTimeout(function(){
func(args)
}, delay)
}
}
//
const debounceNameSearch = debounce(handleNameSearch, 500)
在上面的代码中,关于防抖函数的实现如下;
- 闭包:
debounce
函数使用闭包来保存定时器的id
变量,这样每次keyup
事件的触发,调用debounce
返回的新函数时,都会记录设置的定时器的id
在闭包之中。 - 抽象:
debounce
接收一个函数func
和一个延迟时间delay
,然后返回一个新的函数,这个新函数会在用户停止输入一定时间后执行func
。 - 定时器:
setTimeout
用于延迟执行函数,而clearTimeout
用于取消尚未执行的定时器;每次输入触发keyup
事件时,都会通过clearTimeout
去清除上一次设置的定时器,重置延迟时间delay
。 - 最后执行: 当用户停止输入并且过了
delay
时间后,func
会被执行。
可以看到,添加了防抖功能后,在我们输入内容时,并不会触发搜索函数handleNameSearch
的调用,而会在我们停止输入后,过了延迟时间后才执行搜索函数。
节流(Throttle)
节流(throttle) 是一种与防抖类似的技术,用于限制函数的执行频率。与防抖不同的是,节流确保函数按照固定的时间间隔执行,即使在这段时间内该事件被触发多次。换句话说,无论事件触发多少次,节流保证函数至少每隔一段时间执行一次。
实现节流
清楚节流的原理后,我们又该怎样实现节流呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流</title>
</head>
<body>
<div class="row">
<div>
没有节流的input <input type="text" id="inputa">
</div>
<div>
节流后的input <input type="text" id="inputc">
</div>
</div>
<script>
const inputa = document.getElementById('inputa')
const inputc = document.getElementById('inputc')
// 耗时耗性能任务 => promise
const ajax = (content) => {
console.log(`ajax request ${content}`);
}
// 节流功能
// 定义的时刻
const throttle = (func, delay) => {
// last 上一次是啥时候执行的
// deferTimer 定时器id
let last, deferTimer // 自由变量
// 才是事件的处理函数
// 定义时,生成时 func delay
// keyup return func 调用时能找到闭包中的自由变量
return (args) => {
// 当前时间,隐式类型转换
let now = +new Date()
if (last && now < last + delay) {
// 触发干掉
clearTimeout(deferTimer)
// 让最后一次输入后,过delay后也能执行一次
deferTimer = setTimeout(()=>{
last = now
func(args)
}, delay)
}else{
last = now // 第一次时间
func(args) // 先执行一次
}
}
}
inputa.addEventListener('keyup', (e)=>{
ajax(e.target.value)
})
// 生成的时候
let throttledFunc = throttle(ajax, 1000) // 给事件绑定节流功能
inputc.addEventListener('keyup', (e)=>{
let value = e.target.value
// googleSuggest 体验,1000
// qps
throttledFunc(value) // 节流函数的执行
})
</script>
</body>
</html>
关于节流函数的功能实现,throttle
函数接收了两个参数,需要添加节流的函数 func
和 函数需要触发的时间间隔 delay
;我们使用闭包的方式存储了两个自由变量, 上一次执行函数的时间戳 last
和 一个在延迟时间过后执行的定时器 deferTimer
;
- 第一次触发:如果
last
等于0,说明是第一次触发,直接执行函数并更新last
。 - 在等待时间之外:如果当前时间减去
last
大于等待时间delay
,则执行函数并更新last
。 - 在等待时间内:如果当前时间减去
last
小于等待时间delay
,则清除之前的定时器并设置一个新的定时器来延迟执行函数;这可以确保用户在最后一次输入后,也能够在等待时间delay
过后,再次执行函数。
可以看到,添加了节流后,尽管输入框正在输入值,函数也会在每个时间间隔过后执行一次。
小结
防抖(debounce)和节流(throttle)是两种常见的前端技术,它们都用于限制函数的执行频率,从而提高应用的性能和响应性。防抖是指在一系列连续的触发事件中,只有在最后一次事件发生之后的一段时间内没有新的触发,才会执行函数,而节流是在一定的时间间隔内只允许执行一次函数。
转载自:https://juejin.cn/post/7400242491779383330