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)
對象