共计 2793 个字符,预计需要花费 7 分钟才能阅读完成。
节流(throttle)指的是:事件频繁触发,然而一个时间段内只响应一次。
理论的使用场景是:我感觉和防抖差不多,都是高频事件的场景。
节流的过程:设置好小周期,这个小周期内如果有事件触发,不论触发几次,周期开端响应一次就好。
和防抖的比拟:防抖是这一波高频事件的最初才响应一次;节流则是,如果高频事件继续较久,继续过程中也会响应一次或几次。
这篇文章的工夫线图片能够帮忙了解:js 防抖和节流
一、setInterval 实现
一开始我比拟死心眼。既然要依据固定的周期来,那就得全局设置 setIntervel 了:
<script>
document.addEventListener('DOMContentLoaded', function () {
var timer = null;
var flag = false;
document.addEventListener('scroll', function () {flag = true;});
timer = setInterval(function () {if (flag == true) {
flag = false;
console.log('you have scrolled during last period');
} else {//console.log('no scrolling during last period');
}
}, 1000);
});
</script>
全局设置 setInterval 之后,每隔 1000ms 就做一次 flag 检测:
如果 flag 为 true,阐明后面 1000ms 的周期内有滚动一次或屡次,能够执行动作(输入“you have scrolled during last period”)。
如果 flag 为 false,不必执行动作,这里为了演示就放了个 else 分支并输入“no scrolling during last period”。
(如果你的浏览器不反对监听 document 的 scoll,试试改成 window 或 document.body;如果周期 1000ms 成果感触不强,试试改成 500、100)
这里的 flag,个别叫节流阀。写到这里想起之前曾经做过轮播图的节流阀了,还是没能马上把思路、代码实现转移利用到这种广泛的节流场景。
二、setTimeout 实现并封装
为什么说刚刚死心眼了呢?因为我看了下面那篇文章的图示之后,认为必须要有间断的周期。实际上没有必要,周期之间能够有距离,不肯定要间断。
也就是说,能够应用 setTimeout 代替 setInterval。构想如下过程:
(1) 第一波高频事件的第一次触发,给予响应(设定 timeout,启动周期)。
(2) timeout 到了,周期也就到了开端,执行动作。
(3-1) 之后如果第一波高频事件还在继续触发,会马上响应(设定第二个 timeout,启动第二个周期);这种状况下两个周期之间没有距离,是间断的周期。
(3-2) 如果第一波高频事件在第一个周期开端前进行了,就不会马上设定第二个 timeout;直到第二波高频事件的第一次触发,才会设定第二个 timeout,启动第二个周期;这种状况下两个周期之间会有距离。
改用 setTimeout 并做封装:
<script>
document.addEventListener('DOMContentLoaded', function () {function throttle(handler, period) {
var flag = true;
var timer = null;
return function () {if (flag == true) {
flag = false;
timer = setTimeout(function () {handler();
flag = true;
}, period);
} else {//console.log('nothing happened during last period');
}
};
}
document.addEventListener('scroll', throttle(function(){console.log('you have scrolled during last period');
}, 1000));
});
</script>
实现。能够比照一下防抖和节流的代码实现,有些相似,区别在于:
(1) 防抖要革除 timeout 再设定 timeout,不会产生反复 timeout。
(2) 节流则是利用节流阀变量来阻挡反复 timeout,等 timeout 到时主动实现、革除。
三、更多:拆解封装的函数以及闭包问题
对我来说返回一个函数还是比拟离奇的,接触还不多。另外,throttle()函数中申明了一个 flag,是否每次 scroll 之后都会调用 throttle()函数并申明了新的 flag 变量?如果同时存在很多 flag 那就乱套了,没法正确判断该不该执行响应动作了。
所以我试着拆解封装的函数,发现必须要将 flag、timer 的申明放在 addEventListener()里面,变成全局变量才能够。拆解如下:
<script>
document.addEventListener('DOMContentLoaded', function () {
var flag = true;
var timer = null;
document.addEventListener('scroll', function () {if (flag == true) {
flag = false;
timer = setTimeout(function () {console.log('you have scrolled during last period');
flag = true;
}, 1000);
} else {//console.log('nothing happened during last period');
}
});
});
</script>
阐明什么问题:
(1) throttle()函数解决 scroll 本来的 handler,相当于批改了原来的 handler,一次性提供了“增强版”的 handler;这种返回函数的模式不存在反复调用,只调用了一次 throttle(),后续 scroll 反复调用的是“增强版”handler。
(2) 拆解后 flag、timer 必须写到 addEventListener()里面,是因为写外面就是写在 handler 外面,handler 是会被反复调用的,就会反复申明 flag、timer。
(3) 封装的 throttle()返回了一个包裹本来 handler 的函数,这里又必须把 flag、timer 写到 return 里面,但不用写到 throttle()里面作为全局变量;不能写到 return 外面情理如 (2),能够写到 throttle() 外面是因为 retutn 的函数援用了 flag、timer,形成了闭包;throttle()没有被屡次调用,所以没有申明新的 flag、timer,加上闭包变量不会被回收,所以这样是 OK 的。