上一篇文章讲了去抖函数,而后这一篇讲一样为了优化性能,下降事件处理频率的节流函数。javascript
节流函数(throttle)就是让事件处理函数(handler)在大于等于执行周期时才能执行,周期以内不执行,即事件一直被触发,那么事件将会按每小段固定时间一次的频率执行。html
打个比方:王者荣耀、英雄联盟、植物大战僵尸游戏中,技能的冷却时间,技能的冷却过程当中,是没法使用技能的,只能等冷却时间到以后才能执行。java
那什么样的场景能用到节流函数呢?
好比:数组
咱们经过一个简单的示意来理解:
闭包
节流函数能够用时间戳和定时器两种方式进行处理。app
<div class="container" id="container">
正在滑动:0
</div>
<script> window.onload = function() { var bodyEl = document.getElementsByTagName("body")[0] } var count = 0; window.onmousemove = throttle(eventHandler, 1000); function eventHandler(e) { var containerEl = document.getElementById("container"); containerEl.innerHTML = "正在滑动: " + count; count++; } function throttle(func, delay) { var delay = delay || 1000; var previousDate = new Date(); var previous = previousDate.getTime(); // 初始化一个时间,也做为高频率事件判断事件间隔的变量,经过闭包进行保存。 return function(args) { var context = this; var nowDate = new Date(); var now = nowDate.getTime(); if (now - previous >= delay) { // 若是本次触发和上次触发的时间间隔超过设定的时间 func.call(context, args); // 就执行事件处理函数 (eventHandler) previous = now; // 而后将本次的触发时间,做为下次触发事件的参考时间。 } } } </script>
复制代码
看时间戳实现版本的效果:
异步
<div class="container" id="container">
正在滑动: 0
</div>
<script> window.onload = function() { var bodyEl = document.getElementsByTagName("body")[0] } var count = 0; window.onmousemove = throttle(eventHandler, 1000); function eventHandler(e) { var containerEl = document.getElementById("container"); containerEl.innerHTML = "正在滑动: " + count; count++; } function throttle(func, delay) { var delay = delay || 1000; var timer = null; return function(args) { var context = this; var nowDate = new Date(); var now = nowDate.getTime(); if (!timer) { timer = setTimeout(function() { func.call(context, args); timer = null; }, delay) } } } </script>
复制代码
看看定时器版实现版本的效果: 函数
对比时间戳和定时器两种方式,效果上的区别主要在于:工具
事件戳方式会当即执行,定时器会在事件触发后延迟执行,并且事件中止触发后还会再延迟执行一次。post
具体选择哪一种方式取决于使用场景。underscore 把这两类场景用 leading 和 trailing 进行了表示。
underscore 的源码中就同时实现了时间戳和定时器实现方式,在调用时能够自由选择要不要在间隔时间开始时(leading)执行,或是间隔时间结束后(trailing)执行。
具体看伪代码和示意图:
<div class="container" id="container">
正在滑动: 0
</div>
<div class="height"></div>
<script> window.onload = function() { var bodyEl = document.getElementsByTagName("body")[0] } var count = 0; // 事件处理函数 function eventHandler(e) { var containerEl = document.getElementById("container"); containerEl.innerHTML = "正在滑动: " + count; count++; } var _throttle = function(func, wait, options) { var context, args, result; // 定时器变量默认为 null, 是为了若是想要触发了一次后再延迟执行一次。 var timeout = null; // 上一次触发事件回调的时间戳。 默认为 0 是为了方便第一次触发默认当即执行 var previous = 0; // 若是没有传入 options 参数 // 则将 options 参数置为空对象 if (!options) options = {}; var later = function() { // 若是 options.leading === false // 则每次触发回调后将 previous 置为 0, 表示下次事件触发会当即执行事件处理函数 // 不然置为当前时间戳 previous = options.leading === false ? 0 : +new Date(); // 剩余时间跑完,执行事件,并把定时器变量置为空,若是不为空,那么剩余时间内是不会执行事件处理函数的,见 else if 那。 timeout = null; result = func.apply(context, args); // 剩余时间结束,并执行完事件后,清理闭包中自由变量的内存垃圾,由于再也不须要了。 if (!timeout) context = args = null; }; // 返回的事件回调函数 return function() { // 记录当前时间戳 var now = +new Date(); // 第一次执行回调(此时 previous 为 0,以后 previous 值为上一次时间戳) // 而且若是程序设定第一个回调不是当即执行的(options.leading === false) // 则将 previous 值(表示上次执行的时间戳)设为 now 的时间戳(第一次触发时) // 表示刚执行过,此次就不用执行了 if (!previous && options.leading === false) previous = now; // 间隔时间 和 上一次到本次事件触发回调的持续时间的时间差 var remaining = wait - (now - previous); context = this; args = arguments; // 若是间隔时间还没跑完,则不会执行任何事件处理函数。 // 若是超过间隔时间,就能够触发方法(remaining <= 0) // remaining > wait,表示客户端系统时间被调整过 // 也会当即执行 func 函数 if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); // 解除引用,防止内存泄露 timeout = null; } // 重置前一次触发的时间戳 previous = now; // result 为事件处理函数(handler)的返回值 // 采用 apply 传递类数组对象 arguments result = func.apply(context, args); // 引用置为空,防止内存泄露 if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 若是 remaining > 0, 表示在间隔时间内,又触发了一次事件 // 若是 trailing 为真,则会在间隔时间结束时执行一次事件处理函数(handler) // 在从触发到剩余时间跑完,会利用一个定时器执行事件处理函数,并在定时器结束时把 定时器变量置为空 // 若是剩余事件内已经存在一个定时器,则不会进入本 else if 分支, 表示剩余时间已经有一个定时器在运行,该定时器会在剩余时间跑完后执行。 // 若是 trailing = false,即不须要在剩余时间跑完执行事件处理函数。 // 间隔 remaining milliseconds 后触发 later 方法 timeout = setTimeout(later, remaining); } // 回调返回值 return result; }; }; window.onmousemove = _throttle(eventHandler, 1000); </script>
复制代码
下面是我画的示意图:
节流和去抖的常见场景
去抖和节流函数都是为了下降高频率事件触发的事件处理频率,从而优化网页中大量重绘重排带来的性能问题。
其区别在于去抖会在高频率事件触发时,只执行一次,节流会在知足间隔时间后执行一次。去抖的 immediate,节流中的 leading, trailing 都是为了尽量知足这类工具函数的不一样使用场景。
欢迎关注个人我的公众号“谢南波”,专一分享原创文章。