前言
防抖(Debounce)和节流(Throttle)在前端开发中主要用于控制函数的执行频率,可以有效地优化前端应用的性能,减少资源消耗,并提供更流畅的用户体验。它们的作用具体如下:
防抖(Debounce)的作用:
- 减少不必要的处理: 防抖可以减少在事件频繁触发时对函数的调用次数,例如在用户输入时,可以减少对搜索请求的发送频率。
- 优化性能: 通过限制函数的执行次数,可以避免在短时间内对资源密集型操作的多次调用,从而提高应用性能。
- 提高响应速度: 当事件触发后,函数不会立即执行,而是等待用户停止触发事件一段时间后再执行,这可以避免在用户操作过程中频繁响应,提高应用的响应速度。
节流(Throttle)的作用:
- 保证函数执行频率: 节流确保在指定的时间间隔内,无论事件触发多少次,函数最多只执行一次。
- 平滑事件处理: 对于滚动、窗口大小调整等频繁触发的事件,节流可以平滑事件的处理,避免因事件触发过于频繁而导致的性能问题。
- 提高用户体验: 通过限制事件处理的频率,可以避免用户在进行滚动或调整窗口大小时出现卡顿或延迟的现象,从而提升用户体验。
防抖和节流在适合的应用场景中使用:
- 防抖: 适用于用户输入、自动完成、搜索建议、窗口大小调整等场景,其中用户的操作可能在短时间内连续发生,但只需要在操作结束后处理一次。
- 节流: 适用于滚动事件、窗口大小调整、鼠标移动等场景,这些事件可能会非常频繁地触发,但只需要在一定时间间隔内响应一次。
正文
防抖(Debounce)
这里引用最经典的例子,各大浏览器的搜索栏基本上都是使用了防抖处理文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
后端编写,添加数据
- 在终端中输入 npm i json-server 驱动后端接口 API
- 把网站需要的各个模块数据都准备好
- users
- posts ...外键关联 users.id = posts.userId
- package.json 加入 "scripts": { "dev": "json-server --watch db.json" }
给服务器后台添加数据 在后端新建个json文件专门添加数据文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
js
{
"users": [
{
"id": "1",
"name": "John"
},
......
],
"posts": [
{
"id": "1",
"title": "Post 1",
"content": "内容 1",
"userId": "1"
},
......
]
}
- 在终端中输入 npm run dev 将数据跑起来,得到服务器API
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
Postman
这里使用到Postman进行服务器的模拟,可以对数据进行增删改查文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
也可以Post新增数据文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
前端的编写
html
<!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="undebounceInput"
placeholder="请输入您要搜索的用户名内容"
>
</div>
<script>
const inputa = document.getElementById('undebounceInput')
function handleNameSeach(e){
const value = e.target.value
fetch('http://localhost:3000/users')
.then(res => res.json())
.then(data => {
const users = data
const filterUsers = users.filter(user => {
return user.name.includes(value)// 可读性高
})
console.log(filterUsers)
})
}
inputa.addEventListener('keyup',handleNameSeach)
</script>
</body>
</html>
实现一个简单的搜索功能。当用户在输入框中输入文字时,会通过fetch向服务器请求数据,并根据用户输入的内容过滤出相应的用户数据。具体实现如下:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
- 获取输入框元素:通过
document.getElementById('undebounceInput')
获取到id为"undebounceInput"的输入框元素。 - 监听输入框的keyup事件:通过
addEventListener('keyup', function(e) {...})
监听输入框的keyup事件,当用户在输入框中输入文字时,会触发这个事件。 - 发送请求并处理响应:在事件处理函数中,通过fetch向服务器发送请求,获取到用户数据。然后将数据转换为JSON格式,并通过filter方法过滤出符合条件的用户数据。
- 使用Array.prototype.filter方法:对
users
数组应用.filter()
方法。这个方法会创建一个新的数组,新数组中的元素是通过检查原数组中的每个元素并返回true
的那些元素。- 筛选条件:箭头函数内的代码决定了哪些用户会被保留在新数组中。这里使用了字符串的
.includes()
方法来检查user.name
(即用户的姓名)是否包含了变量value
(即输入框中的文本)。如果包含,.includes()
方法返回true
,表示该用户应被保留。- 除了能使用
.includes()
筛选也能使用.indexOf
进行检测,如果包含则返回数字1
- 输出过滤后的用户数据:将过滤后的用户数据输出到控制台中。
但触发的频率太高,每每按下任意按键都会触发,可能导致CUP红温 定义了一个防抖(debounce)函数,并将其应用于输入框的keyup事件处理器中,以优化搜索功能,减少不必要的API请求。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16525.html
js
function debounce(func, delay) {
// 返回值必须得是函数 让keyup 事件处理函数
return function(args) {
clearTimeout(func.id)
// 函数是一个对象,id挂在func上 func是闭包中的自由变量
func.id = setTimeout(function() {
func(args)
},delay)
}
}
const debounceNameSeach = debounce(handleNameSeach, 500)
inputa.addEventListener('keyup',handleNameSeach)
- 防抖函数
debounce
定义:- 参数:
func
: 需要进行防抖处理的实际回调函数,在这个例子中是handleNameSeach
。delay
: 延迟时间,单位是毫秒,在用户停止操作后,等待这个时间长度再去执行func
,这里是500毫秒。
- 功能:
debounce
函数返回一个新的函数,这个新函数会在每次调用时清除当前的延时器(如果有),然后重新设置一个新的延时器。这意味着,如果在指定的延迟时间内(如500ms)又有新的触发事件,之前的延时操作就会被取消,重新开始计时。只有当用户停止操作超过指定延迟时间后,func
才会被执行。
- 参数:
- 变量
debounceNameSeach
:- 通过将实际的处理函数
handleNameSeach
和延迟时间500毫秒传递给debounce
函数,创建了一个防抖版本的搜索处理函数debounceNameSeach
。
- 通过将实际的处理函数
- 事件监听修改:
- 原先直接将
handleNameSeach
绑定到keyup
事件上可能会导致频繁的网络请求。现在,通过将debounceNameSeach
绑定到keyup
事件上,实现了在用户连续快速输入时,仅在用户停止输入一段时间(500毫秒)后才执行搜索操作,从而提高了效率并减轻了服务器压力。
- 原先直接将
在这个代码片段中,
args
是一个参数,它代表了传递给返回的 debounced 函数的实际参数。当debounceNameSeach
被调用时(例如,在inputa
的keyup
事件中),传入的任何参数都会作为args
被接收。具体来说,
debounce
函数创建了一个防抖动(debounce)版本的函数,用于限制高频触发的函数执行频率。在这个过程中,func
是你希望防抖的原始函数(在这里是handleNameSeach
),而delay
是每次执行之间需要等待的时间(以毫秒为单位)。当防抖后的函数(即
debounceNameSeach
)被调用时,它会接收所有传给它的参数,并将这些参数暂存于args
中。然后,这个防抖函数内部会清除当前可能存在的 timeout(如果有的话),并设置一个新的 timeout 来延迟调用func
(这里是handleNameSeach
)。这样一来,如果在指定的delay
时间内debounceNameSeach
被频繁调用,实际的handleNameSeach
只会在最后一次调用后的delay
毫秒后执行一次,并且会使用最后一次调用时的args
参数。
当用户在 input
框中键入时,事件处理的顺序如下:
- 用户每输入一个字符,都会触发
keyup
事件。 debounceNameSeach
函数被调用,但由于防抖机制,handleNameSeach
函数并不会立即执行。- 如果用户在 500 毫秒内继续输入,之前的定时器会被清除,并重新设置一个新的定时器。
- 当用户停止输入超过 500 毫秒时,
handleNameSeach
函数执行,获取当前输入框的值,并发送请求到服务器搜索用户。 - 服务器响应后,
handleNameSeach
函数处理响应数据,过滤出匹配的用户列表,并在控制台打印。
整个过程中,用户输入的值是作为参数传递给 handleNameSeach
函数的,然后在该函数内部被用来请求数据和过滤用户。
节流(Throttle)
理解完防抖的细节,节流便也基本拿下,因为他们大同小异,都是闭包,都使用到定时器
与防抖触发时机不同:
- 防抖:只有在停止触发事件后,经过指定的时间间隔,函数才会执行一次。如果在这段时间内再次触发事件,则会重置计时器。
- 节流:在指定的时间间隔内,无论触发了多少次事件,函数最多只执行一次。如果事件触发的频率超过了这个间隔,超出的事件会被忽略。
html
<!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>
<div>
没有节流的input <input type="text" id="inputa" />
</div>
<div>
节流后的input <input type="text" id="inputb" />
</div>
</div>
<script>
const inputa = document.getElementById('inputa')
const inputb = document.getElementById('inputb')
const ajax = (content) =>{
console.log(`ajax request ${content}`);
}
// 节流功能
const throttle = (func, delay) => {
let last,deferTimer // 自由变量
// 定义时,生成 func,delay 的闭包
// keyup return func 调用时能找到闭包中的自由变量
return function(args) {
let now = +new Date()
if (last && now - last < delay) {
clearTimeout(deferTimer)
deferTimer = setTimeout(function() {
last = now
func(args)
}, delay)
}else {
last = now
func(args)
}
}
}
inputa.addEventListener('keyup', function(e) {
ajax(e.target.value)
})
let throttledFunc = throttle(ajax, 5000);
inputb.addEventListener('keyup', function(e) {
let value = e.target.value;
throttledFunc(value);
})
</script>
</body>
</html>
定义了一个名为 throttle
的高阶函数,它接收两个参数:func
(一个需要被节流处理的函数)和 delay
(一个数值,表示两次函数调用之间的最小时间间隔,单位为毫秒)。
- 变量声明:在
throttle
内部,首先声明了两个变量last
和deferTimer
。last
用来记录上一次调用func
的时间戳,初始化为undefined
;deferTimer
用来存储setTimeout
的返回值(即定时器ID),以便在需要时取消定时器。 - 返回新函数:
throttle
函数返回一个新的匿名函数。这个新函数就是被节流处理过的func
版本,它接收一个参数args
,这个参数会被透传给原函数func
。 - 计算当前时间:在新函数内部,首先获取当前时间戳
now
,通过+new Date()
实现,这里利用了+
运算符对日期对象进行隐式类型转换得到时间戳数值。 - 判断是否需要节流:接下来的
if
语句检查是否需要节流。如果last
已经有值(即不是第一次调用),并且当前时间和上一次调用时间的差值now - last
小于设定的delay
,说明应该节流,不立即执行func
。- 在需要节流的情况下,首先清除之前设置的定时器(如果有),防止累积定时器导致
func
在一段时间后被多次执行。然后,重新设置一个新的定时器,在delay
毫秒后执行func
,同时更新last
为当前时间now
。
- 在需要节流的情况下,首先清除之前设置的定时器(如果有),防止累积定时器导致
- 首次调用或无需节流时直接执行:如果上述条件不满足,即第一次调用或超过了
delay
时间间隔,则直接执行func(args)
,并将last
更新为当前时间now
,表示本次调用已完成。
调用节流组件
在之前的文章图片懒加载中介绍还介绍过另外一种过节流的使用情景让网页既快速又稳定的方法——图片懒加载(LazyLoad) - 掘金 (juejin.cn)
在运用到鼠标滚动触发监听事件时会发现,滚动触发得太敏感了,轻微滚动便给浏览器发送给了上百条HTTP请求,而我们都知道服务器的链接数是有上限的,如果你不想你的CPU冒烟或者内存爆炸的话就得限制它的请求。
在这里使用节流的方法是最合适不过的,这些事件会非常频繁地触发,只需要在一定时间间隔内让其响应一次。
解决监听事件太敏感,滑动时事件次数过多会导致存在内存占用过高问题
导入方法库lodash
html
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
使用了lodash
库(通过_
符号表示)中的throttle
函数来创建一个新的函数throttleLayLoad
。对原始loadImage
函数的一个节流,限制loadImage
函数的执行频率,无论loadImage
函数被调用得多频繁,throttleLayLoad
都会确保它至少每隔200毫秒才执行一次。
更改鼠标滚动触发的函数为新的函数throttleLayLoad
js
const throttleLayLoad = _.throttle(loadImage,200)
window.addEventListener('scroll',throttleLayLoad);
总结
- 防抖:只有在停止触发事件后,经过指定的时间间隔,函数才会执行一次。如果在这段时间内再次触发事件,则会重置计时器。
- 节流:在指定的时间间隔内,无论触发了多少次事件,函数最多只执行一次。如果事件触发的频率超过了这个间隔,超出的事件会被忽略。
评论