什么是throttle(节流)

Throttling enforces a maximum number of times a function can be called over time.

简单来说就是你假设给定一个wait表示这在个时间内该函数最多可以被执行一次。我们知道知道浏览器scroll触发事件的频率非常高,如果不使用节流的话,我们轻轻一滚动鼠标滑轮可能就触发了10来次某个添加到scroll事件的函数。但如果我们使用节流这个技术的话,我们设置wait为1000(ms),当我们不停地滚动滑轮10s,函数最多被执行10次。10000 / 1000 = 10

最简单的节流

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

这个函数利用闭包返回一个函数,而且它有两个重要的特点:

  1. 当两次函数触发的时间间隔大于wait时,func才会被调用
  2. 第一次触发时func会被调用

underscore中的throttle

  // Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
_.throttle = function(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {}; var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
}; var throttled = function() {
var now = _.now();
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;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
}; throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
}; return throttled;
};

咋一看这个函数的实现比当初那个简单的函数长了很多, 别怕因为他们的思想是一模一样的,多余的代码只是为了一些额外的特性,并不复杂。

首先多了一个options参数,它是一个对象,可以设置leadingtrailing属性。leading是提前领先的意思,在那个简单的版本中我们知道函数在第一次触发时候func是会被触发的,这就是leading。所以当我们没有设置{leading: false}时候,func会在第一次函数触发时候马上被执行。但是当我们显性地传入{leading: false}时候,func就不会马上执行。这是因为if (!previous && options.leading === false) previous = now; 开始previous为0那么条件均为真,previous = nownow - previous > wait不成立。

即第一次触发函数会进入到

else if (!timeout && options.trailing !== false) {
// var remaining = wait - (now - previous);
// now = previous;因此later会在wait毫秒后被执行
timeout = setTimeout(later, remaining);
}

再来看看later

var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
其实写成这样更号理解
var later = function() {
previous = options.leading === false ? 0 : _.now();
// 为了让将previous设为0,是让if (!previous && options.leading === false)再次成立
// 意思就是当超过wait的时间没去触发函数了,再次触发时候的这次也算是首次,它不能马上被执行。(想象就是不断滑动滚轮10s,然后放下鼠标去喝口水,再回来滑滚轮,那应该算作新的一次开始,而不是上次的继续)
result = func.apply(context, args);
timeout = context = args = null;
};

但是如果第二次触发与第一次触发的时间间隔大于wait时候就会进入到

// 实际上remaining<=0就足够了,后者是考虑到假如客户端修改了系统时间则马上执行func函数
if (remaining <= 0 || remaining > wait) {
// 取消第一次的setTimeout
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
}
其实也应该写成这样更好理解
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
}
previous = now;
result = func.apply(context, args);
timeout = context = args = null;
}

有个疑问就是imeout = setTimeout(later, remaining), remaining等于wait,如果两次时间间隔十分接近wait的又大于wait应该是怎么样的流程呢。个人觉得应该是进入到上面这个代码块然后clearTimeout, 为什么呢,首先javaScript是单线程的,setTimeout的意思是将函数在wait毫秒后添加到任务队列中,而不是立即执行。所以理论上来讲还是进入上述代码块要比在执行later()早。但是想想如果每次都是setTimeout也行,每隔wait运行later,效果差不多。

小结

所以第三个参数不传入就是leading模式。

{trailing: false}也是leading模式但和不传参数还是有点区别就是它无法执行timeout = setTimeout(later, remaining);

{leading: false}就是trailing模式,他的timeout = setTimeout(later, remaining);实际上是timeout = setTimeout(later, wait)

解析underscore中的throttle的更多相关文章

  1. 解析underscore中的debounce

    先奉上源码 取自Underscore.js 1.9.1的debounce _.debounce = function(func, wait, immediate) { var timeout, res ...

  2. 理解Underscore中的节流函数

    上一篇中讲解了Underscore中的去抖函数(_.debounced),这一篇就来介绍节流函数(_.throttled). 经过上一篇文章,我相信很多人都已经了解了去抖和节流的概念.去抖,在一段连续 ...

  3. 理解Underscore中的_.bind函数

    最近一直忙于实习以及毕业设计的事情,所以上周阅读源码之后本周就一直没有进展.今天在写完开题报告之后又抽空看了一眼Underscore源码,发现上次没有看明白的一个函数忽然就豁然开朗了,于是赶紧写下了这 ...

  4. 关于 underscore 中模板引擎的应用演示样例

    //关于 underscore 中模板引擎的应用演示样例 <!doctype html> <html> <head> <meta charset=" ...

  5. 深入解析Underscore.js源码架构

    Underscore.js是很有名的一个工具库,我也经常用他来处理对象,数组等,本文会深入解析Underscore源码架构,跟大家一起学习下他源码的亮点,然后模仿他写一个简单的架子来加深理解.他的源码 ...

  6. 浅解析js中的对象

    浅解析js中的对象 原文网址:http://www.cnblogs.com/foodoir/p/5971686.html,转载请注明出处. 前面的话: 说到对象,我首先想到的是每到过年过节见长辈的时候 ...

  7. 深入解析Javascript中this关键字的使用

    深入解析Javascript中面向对象编程中的this关键字 在Javascript中this关键字代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.比如: function TestFun ...

  8. js中eval详解,用Js的eval解析JSON中的注意点

    先来说eval的用法,内容比较简单,熟悉的可以跳过eval函数接收一个参数s,如果s不是字符串,则直接返回s.否则执行s语句.如果s语句执行结果是一个值,则返回此值,否则返回undefined. 需要 ...

  9. 2dx解析cocosbuilder中使用layer时的缺陷

    2dx解析cocosbuilder中使用layer时的缺陷 cocos2d-x 3.7 cocosbuilder中的layer通常会用到触摸属性: 但是在2dx解析布局文件的时候,却很多属性都没解析: ...

随机推荐

  1. 七 Spring的分模块开发的配置,保存客户案例

    Spring的分模块开发的配置 加载配置文件的时候,加载多个 在一个配置文件中引入多个配置文件(常用) 保存客户案例 applicationContext.xml: <?xml version= ...

  2. Spring学习(五)

    自动装备 1.定义 自动装配(autowiring): 将某个Bean实例中所引用的其它的Bean自动注入到当前Bean实例中 自动装配就是指由Spring来自动地注入依赖对象,无需人工参与. 自动装 ...

  3. ProgressBarForm 进度条

    ProgressBarForm public partial class ProgressBarForm : Form { private Panel panel1 = new System.Wind ...

  4. uniapp - 手机调试 ( vivo )

    打开开发者选项,打开USB设置,把默认USB选项改成MIDI模式,就可以检测到手机

  5. redis集群在线迁移

    地址规划 主机名 ip地 端口 redis01 10.0.0.10 6379.6380 redis02 10.0.0.60 6379.6380 redis03 10.0.0.61 6379.6380 ...

  6. 学术Essay写作简单且稳定的架构解析

    学术essay写作(academic writing),无论是论文还是专著,间架要稳固,才有可读性,才有说服力. 稳,有几个应然特征:部块(parts)关联紧密:部块不外生枝叶:部块之间没有杂质干扰. ...

  7. 五 RequestMapping的使用

    1 设置路径映射为数组,在Controller类中一个方法对应多个映射路径,可以被多个url访问 2 分目录管理,在Controller类上添加Request Mapping注解,url访问必须添加相 ...

  8. 新闻网大数据实时分析可视化系统项目——5、Hadoop2.X HA架构与部署

    1.HDFS-HA架构原理介绍 hadoop2.x之后,Clouera提出了QJM/Qurom Journal Manager,这是一个基于Paxos算法实现的HDFS HA方案,它给出了一种较好的解 ...

  9. JDBC--调用函数&存错过程

    1.通过Connection对象的prepareCall()方法创建CallableStatement对象的实例,在使用prepareCall()方法时需传入一个String类型的字符串,该字符串用于 ...

  10. php 实现店铺装修4

    /** * @title 发布装修的店铺 * @example FlagShipShopDecorate.fabu? 调试参数:{"username":"17721355 ...