当前端开发遇上相声:函数防抖与节流的逗趣演绎
【开场白】 话说从前啊,有个地方叫前端村,村里的程序员们个个都是高手,但最近他们遇到了个难题,那就是用户操作太频繁,服务器累得直喘气。怎么办呢?别急,咱村有两位奇人,一位叫“防抖侠”,另一位叫“节流仙”。今天咱们就来讲讲他们如何联手拯救前端村的故事。
【防抖侠登场】 防抖侠,江湖人称“耐心大师”,他最擅长的就是让人冷静下来。比如说吧,村里有个搜索框,用户一激动,手指头就跟开了挂似的,键盘啪啪啪地响。这要是没个节制,服务器非得被折腾得七荤八素不可。防抖侠说:“别急,让我来。”他施展绝技“耐心等待”,告诉用户:“你先歇会儿,等你手停了500毫秒,我再帮你找你要的东西。”这样一来,用户操作再快,也得等那半秒钟过去,服务器这才松了口气,再也不怕用户的手速了。
【实战演练】 一天,防抖侠路过村口,看到村长正愁眉苦脸地对着电脑。原来,村长正在用一个输入框录入村民信息,可是每按下一个键,电脑就吭哧吭哧地响应,就成下面那样了。
“防抖侠,快救救我!”村长哭诉道。防抖侠微微一笑,掏出他的“防抖秘籍”:
// 闭包
function debounce(func, delay) {
// 返回值必须得是函数 keyup 事件处理函数
return function (args) {
clearTimeout(func.id); // 清除定时器
// 对象 , id挂载到func对象上 func是闭包中的自由变量
func.id = setTimeout(() => func(args), delay);
};
}
input框就变成这样了
他把这招教给了村长,村长立刻对输入框施了魔法,从此,无论村长怎么狂敲键盘,输入框都稳如泰山,只有在村长真正停下手指,那半秒钟过后,才开始干活,效率嗖嗖地往上窜。
【节流仙出场】 说完防抖侠,咱再聊聊节流仙。节流仙是个守规矩的人,他的信条是“有序才是王道”。他发现,有些用户喜欢玩“疯狂滚动大赛”,屏幕上下翻飞,服务器忙得团团转。节流仙说:“不行,得给他们立个规矩。”于是,他施展“定时通行令”,告诉用户:“每秒钟只能请求一次,多了不行。”
【实战演练】 节流仙来到村东头的公告栏前,那里贴满了各种通知,村民们经常来来回回查看。但是,每有人经过,公告栏就自动刷新,搞得服务器不堪重负。搞成这样了
“节流仙,你得帮帮我们啊!”村民们围了过来。节流仙点点头,施展“节流神功”:
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);
}
}
}
就变成这样了
他把这招传授给公告栏管理员,管理员一试,果然奏效。现在,无论多少人围观,公告栏都从容不迫,每秒钟只刷新一次,服务器也终于可以喘口气了。
【结尾】 就这样,防抖侠和节流仙联手,用他们的智慧和技巧,解决了前端村的大麻烦。从此,前端村的程序运行得又快又稳,村民们的生活更加美好。记住,无论是防抖还是节流,都是为了让我们的应用更加高效,用户体验更加顺畅。下次你遇到类似的难题,不妨想想防抖侠和节流仙的故事,说不定就能找到解决之道呢!
【谢幕】 好了,各位看官,咱们今天的相声就讲到这里,希望大家喜欢。记住,编程也可以很有趣,就像咱们的防抖侠和节流仙一样,用智慧解决问题,让生活充满欢笑。下次见,拜拜!
在上述故事中,防抖侠与节流仙分别代表了前端开发中两种常用的技术——防抖(Debounce)和节流(Throttle)。这两种技术主要用于优化用户交互和提高系统性能,尤其在处理高频触发事件时特别有效。
正文
防抖(Debounce)
作用: 防抖的主要目的是避免在短时间内连续多次执行同一函数,直到最后一次触发后的一段时间内不再有新的触发,才会执行该函数。它能有效减少不必要的函数调用,减轻服务器负担,提升用户体验。
使用场景:
- 搜索框输入:当用户在搜索框中输入文本时,每次按键都会触发事件,频繁的请求会增加服务器负载。使用防抖可以让系统在用户停止输入一段时间后再发送请求,避免了不必要的查询。
- 窗口大小调整:浏览器窗口大小改变时也会触发事件,频繁调整可能导致页面布局频繁重新计算。防抖可以确保在用户完成调整后才重新计算布局,提高性能。
节流(Throttle)
作用: 节流技术限制了函数执行的频率,确保在指定的时间间隔内,函数最多只能执行一次。这有助于控制资源消耗,防止过度使用CPU或网络资源。
使用场景:
- 滚动监听:当用户快速滚动页面时,每一帧都会触发滚动事件,如果直接响应这些事件,可能会导致页面卡顿。使用节流可以保证在一定时间间隔内只响应一次滚动事件,保持页面流畅。
- 地图缩放:地图应用中,频繁的缩放操作会触发多次重新渲染,通过节流可以确保在用户完成缩放动作后,才进行一次渲染,提高地图加载速度。
实现方式
防抖和节流的实现通常依赖于setTimeout
和clearTimeout
函数,通过设置定时器来控制函数的执行时机。
防抖示例代码:
function debounce(func, delay) {
// 返回值必须得是函数 keyup 事件处理函数
return function (args) {
clearTimeout(func.id); // 清除定时器
// 对象 , id挂载到func对象上 func是闭包中的自由变量
func.id = setTimeout(() => func(args), delay);
};
}
在示例中,debounce
函数接收一个函数func
和一个延迟时间delay
作为参数,返回一个新的函数。当这个新函数被调用时,它会清除之前可能存在的定时器,并设置一个新的定时器,在delay
毫秒后执行原函数func
。如果在这delay
毫秒内又有新的调用,则会重置定时器,直到最后一次调用后的delay
毫秒内没有新的调用,func
才会被执行。
详细解析
-
函数定义:
debounce
函数接收两个参数:func
是一个需要防抖处理的函数,delay
是一个数字,表示延迟执行的时间(通常是毫秒)。
-
返回一个包装函数:
debounce
函数的主体是一个立即执行的返回函数,这个返回的函数接受一个参数args
,这实际上是要传递给func
的参数。
-
清除现有定时器:
clearTimeout(func.id);
这行代码的作用是清除之前可能存在的定时器。func.id
是一个属性,存储了之前设置的定时器的ID。当func
再次被调用时,如果已经有一个定时器在等待执行,那么这个定时器就会被清除,以确保func
不会在延迟时间结束后重复执行。
-
设置新的定时器:
func.id = setTimeout(() => func(args), delay);
这里创建了一个新的定时器,它将在delay
毫秒后执行func
函数。func
函数的参数通过args
传递。同时,定时器的ID被存储在func.id
中,以便于后续的调用可以清除这个定时器。
节流示例代码:
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
,如果当前时间和last
之间的差小于delay
,则不会立即执行func
,而是设置一个定时器,在delay
毫秒后执行func
并更新last
的时间。如果当前时间与last
之间的差大于等于delay
,则直接执行func
并更新last
。
详细分析
-
定义节流函数:
throttle
是一个箭头函数,接收两个参数:func
是要节流的函数,delay
是两次调用之间允许的最小时间间隔(毫秒)。
-
自由变量初始化:
let last, deferTimer;
这里声明了两个变量:last
用于存储上一次函数执行的确切时间戳,deferTimer
用于存储定时器的ID。
-
返回一个包装函数:
- 返回的匿名函数接受任意数量的参数
...args
,这些参数将被传递给func
。
- 返回的匿名函数接受任意数量的参数
-
获取当前时间戳:
let now = +new Date();
这行代码获取当前的时间戳,并将其转换为数字类型。+
运算符在这里用于隐式类型转换。
-
节流逻辑:
if (last && now - last < delay)
这个条件检查last
是否存在(即func
是否已经被执行过),以及当前时间与上次执行时间的差是否小于delay
。如果满足条件,说明func
在设定的时间间隔内被多次调用。
-
处理在时间间隔内的调用:
clearTimeout(deferTimer);
清除之前设置的定时器,以防重复执行。deferTimer = setTimeout(() => { ... }, delay);
设置一个新的定时器,延后delay
毫秒后执行func
。在这个定时器的回调函数中,更新last
为当前时间,并执行func
。
-
处理不在时间间隔内的调用:
- 如果条件
now - last < delay
不成立,即func
的调用间隔超过了delay
,则直接更新last
为当前时间,并立即执行func
。
- 如果条件
源码
防抖
<!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="unDebounced" placeholder="请输入您要搜索的用户名">
<br>
<br>
有防抖的input
<input type="text" id="debounced" placeholder="请输入您要搜索的用户名">
</div>
<script>
// 不加防抖的
let inputa = document.getElementById('unDebounced');
inputa.addEventListener('keyup', (e) => {
const value = e.target.value;
fetch("http://localhost:3000/users")
.then(res => res.json())
.then(data => {
const users = data;
// 数组上的新方法,map是转换
const filterUsers = users.filter(user => {
console.log(user);
// 可读性更好 es6 字符串新增方法
return user.name.includes(value);
// return user.name.indexOf(value) !== -1;
})
console.log(filterUsers);
});
});
// 加了防抖的
const inputb = document.getElementById('debounced');
function handleNameSearch(e) {
const value = e.target.value;
fetch('http://localhost:3000/users')
.then(res => res.json())
.then(data => {
const users = data;
// 数组上的新方法,map是转换
const filterUsers = users.filter(user => {
console.log(user);
// return user.name.includes(value);
return user.name.includes(value);
})
console.log(filterUsers);
});
}
// 闭包
function debounce(func, delay) {
// 返回值必须得是函数 keyup 事件处理函数
return function (args) {
clearTimeout(func.id); // 清除定时器
// 对象 , id挂载到func对象上 func是闭包中的自由变量
func.id = setTimeout(() => func(args), delay);
};
}
const debounceNameSearch = debounce(handleNameSearch, 1000);
inputb.addEventListener('keyup', debounceNameSearch);
</script>
</body>
</html>
- json-server:先初始化:
npm init -y
,再下载安装依赖npm install json-server
db.json
{
"users": [
{
"id": "1",
"name": "wyf"
},
{
"id": "2",
"name": "lbw"
},
{
"id": "3",
"name": "xzq"
}
],
"posts": [
{
"id": "7385778376275165225",
"title": "两数之和你会,三数之和你也会吗?o_O",
"userId": "1"
},
{
"id": "7385778376275165225",
"title": "前端新手小白的第一次全栈式开发(AI聊天室)",
"userId": "2"
},
{
"id": "7385098943941771318",
"title": "从简单的求和掌握“柯里化",
"userId": "3"
},
{
"id": "7380274613187084307",
"title": "前端性能优化实战:掌握图片懒加载技巧",
"userId": "1"
}
]
}
最后在终端 npm run dev
启动json-server
节流
<!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="unDebounced" placeholder="请输入您要搜索的用户名">
<br>
<br>
有防抖的input
<input type="text" id="debounced" placeholder="请输入您要搜索的用户名">
</div>
<script>
// 不加防抖的
let inputa = document.getElementById('unDebounced');
inputa.addEventListener('keyup', (e) => {
const value = e.target.value;
fetch("http://localhost:3000/users")
.then(res => res.json())
.then(data => {
const users = data;
// 数组上的新方法,map是转换
const filterUsers = users.filter(user => {
console.log(user);
// 可读性更好 es6 字符串新增方法
return user.name.includes(value);
// return user.name.indexOf(value) !== -1;
})
console.log(filterUsers);
});
});
// 加了防抖的
const inputb = document.getElementById('debounced');
function handleNameSearch(e) {
const value = e.target.value;
fetch('http://localhost:3000/users')
.then(res => res.json())
.then(data => {
const users = data;
// 数组上的新方法,map是转换
const filterUsers = users.filter(user => {
console.log(user);
// return user.name.includes(value);
return user.name.includes(value);
})
console.log(filterUsers);
});
}
// 闭包
function debounce(func, delay) {
// 返回值必须得是函数 keyup 事件处理函数
return function (args) {
clearTimeout(func.id); // 清除定时器
// 对象 , id挂载到func对象上 func是闭包中的自由变量
func.id = setTimeout(() => func(args), delay);
};
}
const debounceNameSearch = debounce(handleNameSearch, 1000);
inputb.addEventListener('keyup', debounceNameSearch);
</script>
</body>
</html>
总的来说,防抖和节流是前端开发中非常有用的技术,能够有效提高应用的性能和用户体验。掌握这两种技术,对于前端开发者来说是非常重要的。