Throttling enforces a maximum number of times a function can be called over time.java
简单来讲就是你假设给定一个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; } } }
这个函数利用闭包返回一个函数,并且它有两个重要的特色:闭包
wait
时,func
才会被调用func
会被调用// 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; };
咋一看这个函数的实现比当初那个简单的函数长了不少, 别怕由于他们的思想是如出一辙的,多余的代码只是为了一些额外的特性,并不复杂。app
首先多了一个options参数,它是一个对象,能够设置leading
和trailing
属性。leading
是提早领先的意思,在那个简单的版本中咱们知道函数在第一次触发时候func
是会被触发的,这就是leading
。因此当咱们没有设置{leading: false}
时候,func
会在第一次函数触发时候立刻被执行。可是当咱们显性地传入{leading: false}
时候,func
就不会立刻执行。这是由于if (!previous && options.leading === false) previous = now;
开始previous
为0那么条件均为真,previous = now
即now - previous > wait
不成立。
即第一次触发函数会进入到函数
else if (!timeout && options.trailing !== false) { // var remaining = wait - (now - previous); // now = previous;所以later会在wait毫秒后被执行 timeout = setTimeout(later, remaining); }
再来看看later
this
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,效果差很少。code
因此第三个参数不传入就是leading模式。
{trailing: false}
也是leading模式但和不传参数仍是有点区别就是它没法执行timeout = setTimeout(later, remaining);
。orm
{leading: false}
就是trailing模式,他的timeout = setTimeout(later, remaining);
其实是timeout = setTimeout(later, wait)
对象