更多内容请参考:我的新博客

在上一篇文章中,我们了解了为什么要限制事件的频繁触发,以及如何做限制:

  1. debounce 防抖
  2. throttle 节流

上次已经说过防抖的实现了,今天主要来说一下节流的实现。

节流

节流的原理很简单:

如果你持续触发事件,每隔一段时间,只执行一次事件。

根据首次是否执行已经结束后知否执行,效果有所不同,实现的方式也有所不同。

我们用leading代表首次是否执行,trailing 代表结束后是否再执行一次。

关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。

使用时间戳

让我们来看第一种方法:使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为0),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

看了这个表述,让我们来写第一版的代码:

function throttle(func, wait) {
var context,args;
var previous = 0; return function() {
var now = +new Date();
context = this;
args = arguments;
if(now - previous > wait) {
func.apply(context,args);
previous = now;
}
}
}

例子依然是用讲 debounce 中的例子,如果你要使用:

container.onmousemove = throttle(getUserAction,1000)

效果演示如下:

我们可以看到:当鼠标移入的时候,事件立即执行,每过1s 会执行一次,如果再 4.2s 停止触发,以后不会再执行事件。

使用定时器

接下来,我们讲讲第二种实现方式,使用定时器。

当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

function throttle(func, wait) {
var timeout;
var previous = 0; return function(){
context = this;
args = arguments;
if(!timeout){
timeout = setTimeout(function(){
timeout = null;
func.apply(context, args)
},wait)
}
}
}

为了让效果更加明显,我们设置wait 的时间为 3s,效果演示如下:

我们可以看到:当鼠标移入的时候,事件不会立刻执行,晃了 3s 后终于执行了一次,此后每 3s 执行以下,当数字显示为 3 的时候,立刻移除鼠标,相当于大约 9.2s 的时候停止触发,但是依然会在第 12s 的时候执行一次事件。

所以比较两个方法:

  1. 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
  2. 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件

双剑合璧

那我们想要一个什么样的呢?

有人就说了:我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!

所以我们综合两者的优势,然后双剑合璧,写一版代码:

function throttle(func, wait) {
var timeout, context, args, result;
var previous = 0; var later = function() {
previous = +new Date();
timeout = null;
func.apply(context, args)
}; var throttled = function() {
var now = +new Date();
//下次触发 func 剩余的时间
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);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}

效果演示如下:

我们可以看到:鼠标移入,事件立刻执行,晃了 3s,事件再一次执行,当数字变成 3 的时候,也就是 6s 后,我们立刻移出鼠标,停止触发事件,9s 的时候,依然会再执行一次事件。

优化

但是我有时也希望无头有尾,或者有头无尾,这个咋办?

那我们设置个 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;
}

取消

在 debounce 的实现中,我们加了一个 cancel 方法,throttle 我们也加个 cancel 方法:

...
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
...

注意

我们要注意 underscore 的实现中有这样一个问题:

那就是 leading:false 和 trailing: false 不能同时设置。

如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了,所以,这个 throttle 只有三种用法:

container.onmousemove = throttle(getUserAction, 1000);
container.onmousemove = throttle(getUserAction, 1000, {
leading: false
});
container.onmousemove = throttle(getUserAction, 1000, {
trailing: false
});

至此我们已经完整实现了一个 underscore 中的 throttle 函数,恭喜,撒花!

参考文章:https://github.com/mqyqingfeng/Blog/issues/26

最新文章

  1. Android声音播放实例代码
  2. Linux服务器的那些性能参数指标
  3. BizTalk开发系列(五) 属性字段
  4. Quartz将Job保存在数据库中所需表的说明
  5. OPenGL中三维图形的矩阵变换
  6. Branch and Bound:分支限界算法
  7. c#拖放
  8. python seq
  9. [cocos2dx]计算scrollview元素的index
  10. select控件变成可输入状态
  11. YUI的UA检测
  12. JS 2016-09-30T22:04:27.5220743+08:00 转换为日期
  13. 自绘XP风格菜单
  14. 去掉input text后面的叉
  15. linux源码编译安装OpenCV
  16. 执行Git命令时出现各种 SSL certificate problem 的解决办法
  17. 独立安装CentOS7.4全记录
  18. 译:3.RabbitMQ Java Client 之 Publish/Subscribe(发布和订阅)
  19. lucene源码分析(6)Query分析
  20. oracle 事务 数据伪列

热门文章

  1. 在linux上安装spark详细步骤
  2. flutter 动画双指放大图片
  3. python爬虫之git的使用(origin说明)
  4. python之路--初识html前端
  5. linux audit审计(6)--audit永久生效的规则配置
  6. Windows开启WMI时一些总结
  7. C#中那些常用的工具类(Utility Class)(一)
  8. Python:matplotlib绘制条形图
  9. Python——Entry、Text控件
  10. How to install Windows 7 SP1 on Skylake