节流(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的。