什么是节流防抖?在学习这个问题前,我们先抛出问题:
假设我现在有这么一个需求:根据input框输入内容打印到控制台
这个问题简单啊,很简单的功能嘛,代码如下:
<body>
<input type="text" id="input">
<script>
let input = document.getElementById('input')
input.addEventListener('keyup',function(){
console.log(input.value)
})
</script>
</body>
没错,这样确实实现了对输入框进行监听,但是。。。。这个触发事件的频率太频繁了吧,仅仅输入6个汉字,居然触发了这么多次keyup事件,并且我希望打印出的也只是当我停止或暂停输入内容时,所打印的内容。
上述代码仅仅是用于打印输入框的值,如果我需要根据输入框的值向后台发送ajax请求呢,这么频繁的发送请求,那得多发送多少次请求啊,那有没有什么办法,可以指定当我停止输入超过1s后再打印log或者是发送ajax请求呢?
那就要用到setTimeout定时器了,我们给定时器一个延迟,只有停止输入1s后才触发某种逻辑,代码如下
<body>
<input type="text" id="input">
<script>
let input = document.getElementById('input')
// 定义一个定时器对象,setTimeout()返回的是一个定时器的id
let timerId = null
// 给input输入框绑定Keyup键盘松开事件
input.addEventListener('keyup',function(){
// 首先对timerId进行判断,如果已经存在有该定时器逻辑,那么清除这个定时器
if(timerId){
clearTimeout(timerId) // 根据timerId清除对应定时器
}
timerId = setTimeout(function(){
console.log(input.value) //打印value
// 执行完定时器逻辑后把定时器置空
timerId = null
},1000)
})
</script>
</body>
此时结果如下:
只有当你超过1000ms时,才会去执行定时器中的逻辑,同时因为我们有对定时器做清除,所以即便停止输入超过1s,当我们再次输入停止时,依旧可以出发当前定时器。
在触发事件时,我们并不立即执行逻辑,而是给出一个预定值,只有当预定值到来我们才执行逻辑,我们称之为防抖。但是上面的代码,没有经过封装,还无法做到重复使用,下面我们进行一次封装,代码如下:
function debounce(fn,delay=500){
let timerId = null
// 这里就用到了闭包
return function (){
if(timerId){
clearTimeout(timerId)
}
timerId = setTimeout(()=>{
// 这里主要是为了解决传入函数中this指向的问题,并且传入函数可能还会携带参数
fn.apply(this,arguments)
timerId = null
},delay)
}
}
// 调用时
input.addEvent('keyup',debounce(function(){
console.log(input.value)
},1000))
// 如果又有input2输入框
input2.addEventListener('keyup',debounce(function (){
console.log(input2.value)
}))
节流:
回顾上面的防抖:它是在指定延时时间之后才执行一次函数
假设现在给某个div元素注册drag拖拽事件,打印当前元素的位置坐标。
- 如果按照防抖的思想,只有在停止拖拽的时候才打印一次位置
但是我想要在可以拖拽的同时每隔100ms打印一次位置呢
<html>
<head>
<style>
#div1{
width:200px;
height:200px;
border:1px solid #333
}
</style>
</head>
<body>
<div id="div1" draggable="true">可拖拽</div>
<script>
let div1 = document.getElementById('div1')
let timerId = null
div1.addEventListener('drag',function(e){
if(timerId){
// 拖拽事件一直不间断被触发,定时器任务是延迟100ms执行的,100ms一到,执行定时器逻辑,打印位置,在这个过程中,timerId有值,拖拽事件虽然一直被触发,但是不做任何操作,只有当timerId为null时,才会去创建一个新的定时器打印位置
return
}
timerId = setTimeout(function (e){
console.log(e.offsetX,offsetY)
timerId = null
},100)
})
</script>
</body>
</html>
下面依旧是封装一个throttle节流函数
function throttle(fn,delay=500){
let timerId = null
return function(){
if(timerId){
return
}
timerId = setTimeout(()=>{
fn.apply(this,arguments)
timerId = null // 清除定时器任务
},delay)
}
}
div1.addEventListener('drag',throttle(function (e){
console.log(e.offsetX,e.offsetY)
}))
这里有个坑需要注意,定时器那行要使用箭头函数,否则会报错,是因为this的指向问题,定时器中的this指向window,所以定时器中使用箭头函数比较好,但是箭头函数有兼容性问题,需要注意
这里仅作为我自己的记录,水平有限
参考:js节流和防抖问题