1、情景重现
当我们实现一个功能,希望实现输入框输入值变化,自动发起搜索请求,我们可能会写出如下代码:
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
input {
padding: 10px;
border: 1px solid #666;
font-size: 18px;
}
</style>
</head>
<body>
<input type="text" id="input" placeholder="输入账号">
<script>
var input = document.getElementById('input');
input.oninput = function () {
console.log(input.value)
}
</script>
</body>
</html>
非常的简单,利用oninput侦测input中value的变化,打印当前的值。但是问题来了,每次变化都会发起请求,于是发生如下情景:
这是一件非常头疼的事情,仅仅是想搜索测试两个字,却(假装)发起了7次请求。试想当1000个用户同时做一件这样的事,服务器将会接受7000次请求,显然不够经济。
同样的情况,window.onresize、window.mousemove触发频率相当高,解决这种困境需要什么方法呢?
2、防抖和节流
函数防抖和函数节流就是非常经典的解决方法。那么这两者有什么区别呢?
- 函数防抖:指触发事件后在规定时间内回调函数只能执行一次,如果在规定时间内又触发了该事件,则会重新开始计时。
- 函数节流:当持续触发事件时,在规定时间段内只能调用一次回调函数。如果在规定时间内又触发了该事件,则什么也不做,也不会像防抖函数一样重新开始计时。
3、函数防抖
应用场景
两个条件:
1、连续的操作会导致频繁的事件回调
2、用户只关心"最后一次"操作的反馈
原理:两者原理是一致的,都是借助计时器setTimeout来实现回调频率的限制。通过定时器将回调函数进行延时,如果在规定时间内继续回调,发现存在之前的定时器,则将该定时器清除,并重新设置定时器。
- 非立即执行版:事件触发->延时->执行回调函数;如果在延时中,继续触发事件,则会重新进行延时,在延时结束后执行回调函数。常见例子:就是input搜索框,客户输完过一会就会自动搜索。
- 立即执行版:事件触发->执行回调函数->延时;如果在延时中,继续触发事件,则会重新进行延时。在延时结束后,并不会执行回调函数。常见例子:就是对于按钮防点击,例如点赞、心标、收藏等有立即反馈的按钮。
我们以上文提到的情景,实现一个函数防抖:
var input = document.getElementById('input');
input.oninput = debounce(function(){
console.log(input.value)
}, 1000)
// 非立即执行
function debounce(fn, delay) {
var timer = null;
return function () {
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(this)
}, delay)
}
}
// 立即执行
function debounce(fn, delay) {
var timer = null;
return function () {
if(!timer){
fn.apply(this)
}
setTimeout(function () {
clearTimeout(timer)
}, delay)
}
}
我们在侦测到value值变化的时候会创建一个计时器,待计时结束之后执行回调函数,但如果在计时间隙,又再一次触发,则会销毁之前的计时器,重新开始计时。关键之处,就是后面所有的回调函数都能访问到之前设置的定时器time,在这里使用闭包,效果如下:
4、函数节流
应用场景:
两个条件:
1、客户连续频繁地触发事件(事件触发的时间间隔至少是要比规定的时间要短)
2、在不断操作过程中的反馈。
例如:鼠标移动事件,滚动事件等
var input = document.getElementById('input');
input.oninput = throttle(function () {
console.log(input.value)
}, 1000)
function throttle(fn, delay) {
// 记录上一次触发的时间戳,初始设为0,确保第一次触发会执行回调
var lastTime = 0;
return function () {
var nowTime = Date.now();
// 如果时间差大于规定时间,则执行回调
if (nowTime - lastTime > delay) {
fn.apply(this)
lastTime = nowTime;
}
}
}
我们在侦测到value值变化的时候会记录下当前时间,只有当,效果如下:
还有另外一种实现方式,和防抖有异曲同工之妙,借助计时器。但区别在于,设置的计时器不会被中途销毁,必须等到计时结束才能进行下一个回调。
//定时器版:
function throttle(fun, delay = 1000) {
let timer;
return function(args) {
let that = this;
//如果定时器不存在,则设置定时器,到时后,才执行回调,并将定时器设为null
if (!timer) {
timer = setTimeout(function(){
timer = null;
fun.apply(that)
}, delay)
}
}
}
感谢大佬的文章,有所启发:https://juejin.im/post/5c6bab91f265da2dd94c9f9e