函式節流介紹
頁面在繫結resize,keydown或者mousemove這些能連續觸發的事件時,使用者只要很常規的操作,就能連續觸發多次繫結的方法。當繫結方法裡面存在大量的類似於DOM操作這種極其消耗效能的程式碼時,會直接導致頁面執行的卡頓。這個時候就會用到函式節流。
函式節流的實現
函式節流最普通的實現就是透過取摩操作來過濾部分執行。參考程式碼如下
javascript
var mousemoveCount = 0; function mousemoveListener(e){ mousemoveCount++; if(mousemoveCount % 2 === 0){ return; } console.info('業務邏輯'); }
當第一次觸發並執行mousemoveListener事件時,會列印“業務邏輯”;緊接著第二次執行mousemoveListener事件時,由於mousemoveCount為2,會直接return掉,並不會列印“業務邏輯”。這樣子,就實現了函式節流,存在複雜計算的業務邏輯執行次數減半了。
但是這種實現存在兩個問題:
- 方法的執行頻率(或者說幀率)是不可控的。比如mousemove事件,執行頻率由滑鼠移動速度決定,由上面這種方式實現,頻率還是由滑鼠移動速度決定。
- 最後一次觸發可能未執行。比如當最後一次觸發事件時,mousemoveCount是偶數,那麼會直接return。如果業務需要最後一次必須執行業務邏輯,則會存在bug。
所以就有了下面的最佳化實現(throttle和debounce)。
throttle實現
throttle又叫函式節流,思路是控制某一個時間段(執行週期)內觸發的事件,只會執行一次業務邏輯。程式碼如下:
javascript
var lastMousemoveTime = 0, mousemoveTime = 100; function mousemoveListener(e){ var now = new Date().getTime(); if(now - lastMousemoveTime <= mousemoveTime) { return; } lastMousemoveTime = now; setTimeout(function(){ console.info('業務邏輯'); }, mousemoveTime); }
第一次觸發mousemove會設定100ms後執行業務邏輯,在這之後的100ms裡面觸發的mousemove都不會觸發業務邏輯。相當於控制了mousemove事件100ms觸發一次,也就是10幀。
使用這種實現(throttle),可以做到觸發頻率可控。但當業務希望連續的觸發事件只在之後一次觸發後才執行業務邏輯,比如resize事件,只希望視窗變化結束後才進行業務邏輯的執行,throttle實現就不適用了。這個時候就需要使用到debounce
debounce實現
debounce又叫函式去抖動,思路是業務邏輯在resize不在觸發後才執行。程式碼如下:
javascript
var resizeTimer = null; function resizeListener(e){ if(resizeTimer) { clearTimerout(resizeTimer); } resizeTimer = setTimeout(function(){ console.info('業務邏輯'); }, 100); }
但resize連續快速觸發時,業務邏輯並不會執行。只有當最後一次觸發resize後100ms,才執行業務邏輯。這種情況就能實現只在最後一次resize觸發業務計算了。
underscore 中已經對throttle和debounce做了實現和封裝, 有興趣可以去檢視原始碼。