概念
在项目开发中避免不了使用防抖节流来限制频繁触发函数。既然要了解这个东西,首先要们要知道什么是防抖什么是节流
防抖(debounce)
在事件被触发n
秒后再执行回调,如果在这n秒内又被触发,则重新计时。
节流(throttle)
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
总结来说,防抖和节流都是防止同一时间内多次触发某个操作,但是其作用和原理又不尽相同。防抖相当于每次触发重新计时,只有最后一次操作有效,举个不起恰当的例子,比如说我们刷新页面时加载页面资源需要2s(只是举个例子,一般不可能会有2s的等待时间),每次刷新都会重新加载。2s内资源没有加载完毕,数据不会渲染,所以每次刷新后2s内再次刷新会重新加载,只有但某次刷新后2s以内没有重新刷新才会触发页面渲染。就,防抖与之同理。
而节流是固定时间段内第一次触发有效,相当于有些游戏里面的技能冷却。当我们触发了节流函数后,在固定时间段内该函数处于冷却状态。不会被触发不会被调用。当冷却时间结束可以被再次调用。
代码实现
防抖实现
这里我们就用按钮触发函数来做简单的实现。我们可以先捋一捋思路,先将按钮上绑定对应函数,该函数并不是最终调用执行的函数,只是用来处理防抖的中间函数。在该函数中调用防抖函数,传入最终需要触发的函数以及防抖时间。在防抖函数中主要是通过定时器来做到固定时间内不被重复触发,那么在短时间内多次触发防抖,重新计时就需要用到计时器清除clearTimeout(timer)
来完成。接下来我们看看具体代码实现吧。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="func()">防抖</button>
</body>
<script type="text/javascript" src="./debounce.js"></script>
</html>
// 函数在一段时间内的多次调用,仅是的最后一次调用有效
function debounce(fun, time = 1000){
let timer = null
return function(){
timer ? clearTimeout(timer) : ''
timer = setTimeout(()=>{
fun.apply(this, arguments)
},time)
}
}
function addOne(val1, val2){
console.log(val1)
console.log(val2)
}
let fd = debounce(addOne,1000)
function func(){
fd(1, 2)
}
其实代码很简单,只是使用了闭包来保持定时器,当多次点击时就清除已存在的计时器,重新计时
节流实现
我们实现了防抖,那么节流该怎么实现,先来分析一下逻辑。同样我们先使用按钮绑定一个中间函数,在该函数中调用节流函数,将最终触发的事件和节流时间段传给节流函数。
在节流函数中我们需要判断从现在往前推进指定的时间段内该函数改节流函数实例是否被触发过,如果被触发过就不执行对应的回调函数,如果未被触发过就执行回调函数。该怎么判断该时间段之前是否被触发过呢?有两种实现方案,第一种与防抖相同,可以使用定时器来处理;第二种我们也可以时间戳来进行判断。
定时器实现节流
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="func()">节流</button>
</body>
<script type="text/javascript" src="./throttle.js"></script>
</html>
let jl = throttle(output, 500)
function func(){
jl(1, 2)
}
function output(val1, val2){
console.log(val1)
console.log(val2)
}
function throttle(fun, delay = 1000){
let timer = null
return function(){
if(!timer){
fun.apply(this, arguments)
timer = setTimeout(() => {
timer = null
}, delay)
}
}
}
我们可以看到用定时器实现节流与防抖非常相像,但是其实现逻辑并不相同。这里我们是判断定时器是否为空,如果为空则执行回调函数且开启一个定时器到指定时间再将定时器至空。如果不为空则什么都不执行,可以说逻辑比防抖还简单。
时间戳实现节流
那么使用时间戳该怎么实现呢,其实就在于每次记录防抖函数实例被调用的时间戳,用本次调用的时间减去上次调用的事件,如果其时间间隔小与规定的时间则不执行回调函数,且不记录本次被触发时间。如果时间间隔大于规定时间,则执行回调函数并记录本次触发回调时间。我们来看具体实现,html
部分和其他部分的代码我们就不重复了,只来看看核心的节流函数代码。
function throttle(fun, delay = 1000){
let lastTime = null
return function(){
let now = Date.now()
if(!lastTime || now - lastTime > delay){
lastTime = now
fun.apply(this, arguments)
}
}
}
应用场景
上面我们也说到了什么是防抖节流,还说到了该怎么去实现一个防抖节流函数来限制一些函数被频繁调用。那么这里我们需要说一下防抖与节流适用于什么样的业务场景。主要是因为在平时项目开发中很多地方都需要做防止频繁触发的需求,但是我们需要清晰的知道具体什么时候该用防抖什么时候该用节流。
防抖
当我们需要实现比如手机号,邮箱等的输入校验;或者对于搜索功能的联想功能时。需要对输入框的用户输入数据进行监听,如果不做处理就会频繁触发校验或者搜索联想接口,影响客户端与服务端的性能。因为我们并不需要在用户输入每一个字符的时候都去对当前数据做校验做联想,我们可以等到用户停止后在做处理,当用户频繁操作输入框内容时不做触发。
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发停止调整后一次函数回调
节流
只要是想要防止频繁调用,并且强调频率调用时基本都需要使用节流。例如点赞功能,频繁点赞前端都做出相应样式,但是调用后端接口时可使用节流按照固定频率调用接口。鼠标不断点击触发,mousedown
(单位时间内只触发一次),鼠标位置监听以及监听滚动事件,比如是否滑到底部自动加载更多,用throttle
来判断,减少浏览器的监听压力。