前言
防抖和节流函数在开发的过程中时常都会用到,它也是函数式编程的一个比较经典的应用,在开发的过程中我们总是免不了要给元素绑定像scroll、mousemove、change等事件监听并执行其相关的回调函数执行相应的函数逻辑。
然而我们往往希望该函数相应的函数逻辑不要太频繁反复的执行,而是能够减少执行的频率。那么使用防抖或者节流函数是一个很好的选择。
假设我们给一个div元素一个mousemove事件,其html代码如下所示:
<!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>Debounce</title>
<style type="text/css">
#box {
width: 400px;
height: 200px;
line-height: 200px;
text-align: center;
margin: 0 auto;
background-color: lightgreen;
color: #fff;
}
</style>
</head>
<body>
<div id="box"></div>
<script type="text/javascript">
let num = 0;
const box = document.getElementById('box');
box.innerHTML = num;
function count() {
box.innerHTML = num++;
};
box.addEventListener('mousemove',count,false);
</script>
</body>
</html>
效果如下动图所示,鼠标在该div元素中进行频繁的移动就会使事件被频繁地触发,从而导致回调函数被频繁的触发,div元素中的数字会疯狂的增长。
如果我们采用防抖或者节流函数,使该回调函数作为函数参数传递给防抖或节流函数,然后防抖或者节流函数返回一个新的函数作为新的回调函数,可以使原来的count函数不会频繁的执行
防抖和节流的效果区别
防抖(debounce)是无论你触发多少次事件,只要相邻两次触发的间隔时间小于你设定间隔时间,就会重新设定间隔时间,如果一直以这样的频率触发,最后函数只会被执行一次
节流(throttle)是连续触发事件的过程中以一定时间间隔执行函数。 比如你用了10秒触发了100次事件,你的间隔时间是1秒,那事实上你触发了100次事件,但是执行了10次,每间隔1秒钟,只会执行一次操作,无论这1秒钟内触发了多少次事件。
防抖(debounce)
简单一句话概括就是,触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。 分为:非立即执行版和立即执行版。
非立即执行版:
function debounce(fn, delay){
let t; //用于接收setTimeout返回的标识;
return function() {
let _this = this,
_arguments = arguments; //arguments中有事件对象
clearTimeout(t);
t = setTimeout(function(){
fn.apply(_this, arguments);
},delay);
}
}
box.addEventListener("mousemove", debounce(count, 1000), "false");
非立即执行版的效果如下,即触发事件之后函数并不会立即执行,会在n秒之后才执行,而如果在n秒内又对事件进行触发,就会重新延迟函数的执行时间。
可以看到,在触发事件后函数 1 秒后才执行,而如果我在触发事件后的 1 秒内又触发了事件,则会重新计算函数执行时间。
而立即执行版的代码如下所示:
function debounce(fn, delay){
let t; //用于接收setTimeout返回的标识;
return function() {
let firstTime = !t;
if(firstTime){
fn.apply(this, arguments);
}
clearTimeout(t);
t = setTimeout(() => {
t = null;
}, delay);
}
}
立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
效果如下:
由于有不立即执行版和立即执行版的存在,我们可以将这两个版本进行合并处理,用第三个参数来决定要执行哪种代码逻辑,代码如下:
function debounce(fn, delay, i){ //i为true则立即执行,i为false则不立即执行
let t;
return function () {
let _this = this;
let args = [...arguments];
if(t){clearTimeout(t)};
if(i){
let first = !t;
t = setTimeout( () => {
t = null;
}, delay);
if(first){
fn.apply(_this, args);
}
}else{
t = setTimeout( () => {
fn.apply(_this, args);
}, delay)
}
}
}
节流(throttle)
节流的效果就是如果n秒中连续地触发事件,则n秒过后只会执行一次函数,也就是说节流是会稀释函数的执行频率的。 节流一般可以分为时间戳版还有延时器版本。
节流时间戳版
代码如下:
function throttle(fn, delay){
let lastTime = 0;
return function () {
let nowTime = +new Date();
if(nowTime - lastTime > delay){
fn.apply(this, arguments);
lastTime = nowTime;
}
}
}
时间戳版效果
节流延时器版
代码如下:
function throttle(fn, delay){
let t;
return function () {
if(!t){
t = setTimeout( () => {
t = null;
fn.apply(this, arguments);
}, delay);
}
}
}
延时器版本的效果
时间戳版和延时器版结合
时间戳版本和延时器版本的区别,如果我们仔细对比两个效果图会发现,时间戳版是在每个时间段的开头就会执行一次,而延时器版本就是在每个时间段的结尾才执行一次。当然这也有结合版本,如果想第一次立即执行,之后是固定间隔时间执行,可以使用这个结合版本。 结合版本的代码如下:
function throttle(fn, delay){
let t,
lastTime = 0;
let which_throttle = function () {
let _this = this,
_arguments = arguments,
nowTime = +new Date(),
interval_time = nowTime - lastTime,
remain_time = delay - interval_time,
later = function () {
t = null;
fn.apply(_this, _arguments);
lastTime = nowTime;
};
console.log(interval_time);
if(t){
clearTimeout(t);
t = null;
}
if(interval_time > delay){
fn.apply(_this, _arguments);
lastTime = nowTime;
}else{
t = setTimeout(function () {
later();
}, remain_time);
}
}
return which_throttle;
}