🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 简介 如果你持续触发事件,每隔一段时间,只执行一次事件。 <br> 根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。 <br> 我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。 <br> throttling的特点在连续高频触发事件时,动作会被定期执行,响应平滑。 <br> 延迟throttling示意图: ![](https://box.kancloud.cn/1f091ab98c126c4947d9b107a29c35b6_1278x932.png) <br> 前缘throttling 示意图: ![](https://box.kancloud.cn/27cf16c266030bca1a7de13bc93542fb_1246x916.png) <br> 关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。 <br> <br> # 使用时间戳 当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。 ~~~ /** * 时间戳实现 */ function throttle1(func, wait) { var previous = 0 // var context, args return function () { var now = +new Date() var args = arguments var context = this if (now - previous > wait) { func.apply(context, args) previous = now } } } ~~~ 效果演示如下: ![](https://box.kancloud.cn/5b5acc0f84a9cec9d0d6d9c7b7dd3c01_489x220.gif) 当鼠标移入的时候,事件立刻执行,每过 1s 会执行一次,如果在 4.2s 停止触发,以后不会再执行事件。 <br> <br> # 使用定时器 当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。 ~~~ /** * 定时器实现 */ function throttle2(func, wait) { var timeout var context, args return function () { context = this args = arguments if (!timeout) { timeout = setTimeout(function () { timeout = null func.apply(context, args) }, wait) } } } ~~~ 为了让效果更加明显,我们设置 wait 的时间为 3s,效果演示如下: ![](https://box.kancloud.cn/38588775db91d564c71cef2002c6b0ba_489x213.gif) 我们可以看到:当鼠标移入的时候,事件不会立刻执行,晃了 3s 后终于执行了一次,此后每 3s 执行一次,当数字显示为 3 的时候,立刻移出鼠标,相当于大约 9.2s 的时候停止触发,但是依然会在第 12s 的时候执行一次事件。 <br> <br> 所以比较两个方法: 1. 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行 2. 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件 <br> <br> # 优化 我们设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,我们约定: * leading:false 表示禁用第一次执行 * trailing: false 表示禁用停止触发的回调 ~~~ function throttle(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : new Date().getTime(); timeout = null; func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = new Date().getTime(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } }; return throttled; } ~~~ <br> <br> # 取消 在 debounce 的实现中,我们加了一个 cancel 方法,throttle 我们也加个 cancel 方法: ~~~js ... throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = null; } ... ~~~ # 注意 我们要注意 underscore 的实现中有这样一个问题: 那就是`leading:false`和`trailing: false`不能同时设置。 如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了,所以,这个 throttle 只有三种用法: ~~~js container.onmousemove = throttle(getUserAction, 1000); container.onmousemove = throttle(getUserAction, 1000, { leading: false }); container.onmousemove = throttle(getUserAction, 1000, { trailing: false }); ~~~ <br> <br> # 参考资料 [防抖(debounce) 和 节流(throttling)](https://blog.csdn.net/hupian1989/article/details/80920324)