开发中的常用场景:
• 页面的scroll事件
• input框等的输入事件
• 拖拽事件用到的mousemove等
这些都是短时间内,高频率触发函数的情况,对于这些情况,我们经常设置函数的防抖和节流去解决这些问题。
那函数防抖节流到底是什么?如何实现呢?下面分别对防抖节流进行介绍。
防抖
基础概念
防抖就是,等待指定的时间,并且指定时间内无再次调用,才执行一次函数。如果在这段时间内调用了,就重新计时,再等到指定时间内无调用时执行函数。
就像一个弹簧,被按下后,没有全部回弹时,再多次按下,则不执行函数。直至弹簧回弹完毕,才会执行函数。
代码实现
实现原理:利用定时器,在函数第一次调用时,设定一个定时器,之后调用时发现已经存在定时器就清空之前的定时器,并重新设定一个新的定时器。如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。具体代码如下:
const debounce = (callback, wait) => {
let timer = null;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
callback.apply(this, args);
}, wait);
}
}
为了提高用户体验,往往第一次调用的时候是需要立马执行函数的。此时,只需要在原来的实现上加上第一次触发立即执行的功能。具体代码如下:
const debounce = (callback, wait, immediate) => {
let timer = null;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
// 添加 第一次触发立即执行 的判断
if (immediate && !timer) {
callback.apply(this, args);
}
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
callback.apply(this, args);
}, wait);
}
}
节流
基础概念
节流就是限制函数执行的频率。在一定时间间隔内,函数只执行一次,并无视这段时间间隔内产生的其他函数调用。即一次调用执行后,后续指定时间范围内的调用忽略。
就像水龙头🚰每2秒只滴下一滴水。(第0s💧,第2s💧, 第4s💧,第6s💧…)
代码实现
方法一:使用时间戳来记录函数调用时刻,通过计算当前调用时间戳与上次调用的时间戳的时间差来判断是否已达到规定等待时间。如果是,就执行函数,并更新上次调用时间戳。否则,忽略此次调用。具体代码如下:
const throttle = (callback, wait) => {
let previous = 0;
return function (...args) {
let now = +new Date();
if (!previous || (now - previous) > wait) {
callback.apply(this,args);
previous = now;
}
}
}
方法二:使用setTimeout。函数调用时,判断当前是否有定时器存在,如果是,就调用函数,并且设置一个定时器延迟指定时间后清空当前定时器。否则,就忽略此次请求。具体代码如下:
const throttle = (callback, wait) => {
let timer = null;
return function (...args) {
if (!timer) {
callback.call(this,...args);
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
}, wait);
}
}
}
实例讲解-常见的名称校验功能
1、没使用函数防抖节流的情况下,普通的名称校验功能就是监听输入框的keyup事件,当输入框内容改变时就触发请求函数(此处用控制台打印代替ajax请求)。具体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>名称重复校验(防抖与节流)</title>
</head>
<body>
<input id="input" style="width: 200px; height: 20px; line-height: 20px; padding: 5px 10px;" />
<script>
window.onload = function () {
var oInput = document.getElementById('input');
oInput.addEventListener('keyup', function (e) {
// 直接请求校验
verifyName(e);
});
function verifyName(e) {
var text = e.target.value;
console.log('校验内容:', text);
}
}
</script>
</body>
</html>
运行结果如下:
只要输入框内容发生变化,就会发出校验请求。由图可见,短短的输入几个字,就发出约20个请求,十分消耗性能。
2、对请求函数添加防抖,进行优化。具体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>名称重复校验(防抖与节流)</title>
</head>
<body>
<input id="input" style="width: 200px; height: 20px; line-height: 20px; padding: 5px 10px;" />
<script>
window.onload = function () {
var oInput = document.getElementById('input');
var timer = null;
oInput.addEventListener('keyup', function (e) {
// 添加防抖
if (timer) {
clearTimeout(timer);
} else {
verifyName(e);
}
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
verifyName(e);
}, 500);
});
function verifyName(e) {
var text = e.target.value;
console.log('校验内容:', text);
}
}
</script>
</body>
</html>
运行结果如下:
添加防抖后,在输入期间确实能够减少请求次数。但是,仍然存在一个问题。假如我不断地输入,输入了很多内容,但是我每两次之间的输入间隔都小于自己设置的wait值,那么,这个请求函数就会一直得不到调用,用户体验就十分不好。所以,实际情况中,我们更希望的是,当达到某个时间值时,一定要执行一次这个请求函数。
3、在添加防抖函数的基础上,进一步优化。通过时间戳判断两次函数调用时间差,当时间差大于一定范围就调用一次请求函数(节流)。具体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>名称重复校验(防抖与节流)</title>
</head>
<body>
<input id="input" style="width: 200px; height: 20px; line-height: 20px; padding: 5px 10px;" />
<script>
window.onload = function () {
var oInput = document.getElementById('input');
var timer = null;
var previous = 0;
oInput.addEventListener('keyup', function (e) {
// 进一步优化 添加时间戳 比较
var now = +new Date();
if (previous && (now - previous) > 500) {
verifyName(e);
previous = now;
} else {
if (timer) {
clearTimeout(timer);
} else {
verifyName(e);
previous = now;
}
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
verifyName(e);
previous = now;
}, 500);
}
});
function verifyName(e) {
var text = e.target.value;
console.log('校验内容:', text);
}
}
</script>
</body>
</html>
运行结果如下:
显然,连续的输入,到一定时间间隔之后,请求函数必然会被调用,但是又不是频繁的调用。这样就达到了性能优化的目的,并且也不影响用户体验。