debounce
原理
监听一个输入框,文字变化后触发change事件,如果直接用keyup事件,则会频繁触发change事件
防抖:用户输入结束或暂停的时候,才会触发change事件
应用场景: search框联想,注册的时候提示是否已注册等
<body>
<div class="test">
<input id='input1'>
</div>
<script>
const input1 = document.getElementById('input1')
// 糟糕的情况 每次输入文字都会输出值
input1.addEventListener('keyup', function () {
console.log(input1.value);
})
</script>
</body>
希望停半秒钟(自定义)发送一个请求,再输入也是停半秒钟的时候发送一个请求
这种机制称为防抖
<body>
<div class="test">
<input id='input1'>
</div>
<script>
const input1 = document.getElementById('input1')
// 糟糕的情况 每次输入文字都会输出值
// input1.addEventListener('keyup', function () {
// console.log(input1.value);
// })
let timer = null
input1.addEventListener('keyup', function () {
// 如果有timer就清掉它
timer&&clearTimeout(timer)
timer = setTimeout(()=>{
// 触发事件
ajax()
// 清空定时器
timer=null
},500)
})
function ajax() {
console.log("ajax---",input1.value);
}
</script>
</body>
现在输入123(间隔小于0.5s) 来根据原理图看一下他的代码执行顺序
封装debounce函数
const input1 = document.getElementById('input1')
input1.addEventListener("keyup", debounce(function(){
console.log(input1.value);
},600))
function debounce(fn, delay = 500) {
// 注意timer是在闭包中的
// 扩展 闭包一般在什么情况下用呢 ? 这个是作为函数作用域返回值
// 外部不会轻易拿到 所以不会导致timer逻辑混乱
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn()
// fn函数在执行的时候,会把当前的this,arguments传进来
// 返回的fn可能会有一些参数,还要把this传进来,如果用this的话那就不能
// 用箭头函数了 只能用function形式了
// fn.apply(this,arguments)
timer = null
}, delay)
}
}
总代码
<!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>
<div class="test">
<input id='input1'>
</div>
<script>
const input1 = document.getElementById('input1')
let a =123
input1.addEventListener("keyup", debounce(ajax,600,a))
function debounce(fn, delay = 500) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this,arguments)
timer = null
}, delay)
}
}
function ajax(aa) {
console.log("ajax---", input1.value);
}
</script>
</body>
</html>
throttle
原理
保持一个频率连续触发
使用场景,拖拽一个元素的时候,要随时拿到该元素被拖拽的位置,比如拖拽排序,需要随时判断位置是否到达了要拖拽到的位置
如果直接用drag事件,则会频繁触发,很容易导致卡顿
这时候需要节流,无论拖拽速度多快,都会每隔100ms触发一次 对计算机来说 这是一个比较宽裕的时间
没有经过节流的代码
<div class="div1" draggable="true">拖拽</div>
const div1 = document.getElementsByClassName('div1')[0]
console.log(div1);
div1.addEventListener("drag",(e)=>{
// console.log(e);
console.log(e.offsetX+' '+e.offsetY);
})
经过节流的代码
const div1 = document.getElementsByClassName('div1')[0]
// console.log(div1);
let timer = null
div1.addEventListener("drag",(e)=>{
// console.log(e);
if(timer){
return
}
timer = setTimeout(() => {
console.log(e.offsetX+' '+e.offsetY);
timer = null
}, 500);
})
这个代码应该好懂
拖拽事件不断被触发,只要timer有值就return 等到设定的时间到了,timer清空,这时候才可以重新计时
封装throttle函数
// 节流封装函数
function throttle(fn, delay = 500) {
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this,arguments)
timer = null
}, delay)
}
}
div1.addEventListener("drag", throttle(function (e) {
console.log(e.offsetX);
}, 100));
apply
apply作用,第一个绑定this,第二个绑定参数
参数在什么地方有用呢
这里是没有问题的
div1.addEventListener("drag",function(e) {
console.log(e);
});
但是在这里
e会传给谁?他会传给throttle返回的函数
这个函数接收e
他不会传给throttle包裹的函数
那如何让包裹的函数也有e呢
那就需要通过apply把arguments传进去
如果是settimeout里面运行fn的话
那么e会是undefined
一篇好文章
https://www.zhihu.com/people/peng-leonard/posts
遇到的问题
疑问 根本就不会进入到回调函数里面
但是这样就可以