瀏覽器一個網頁的UI線程只有一個,他同時會處理界面的渲染和頁面JavaScript代碼的執行(簡單擴展一下,瀏覽器或者JavaScript運行大環境並不是單線程,諸如ajax異步回呼、hybrid框架內與native通信、事件隊列、CSS運行線程等等都屬於多線程環境,不過ES6引入了Promise類別來減少了部分異步情況)。因此當JavaScript程式碼運行計算量很大的方法時,就有可能阻塞UI線程,小則導致使用者回應卡頓,嚴重的情況下瀏覽器會提示頁面無回應是否強制關閉。例如網頁的頁面捲動事件、行動裝置的滑動、縮放事件等。即使沒有出現嚴重的效能問題,我們也應該站在效能最佳化的角度將短時間內會多次觸發的大規模處理時間進行分流計算。
如何有效避免UI執行緒運行過長的程式碼,是所有使用者互動應用程式需要考慮的問題,同樣的問題在客戶端android可以使用UI主執行緒開子執行緒來分散運算。與此對應的,js也可以透過引入webWorker來分散計算,但是在js中有一個更簡單且效果不錯的方法:函數節流。使用函數節流的核心技巧就是使用定時器分段計算。具體的實現方式大致上有兩種思路。
1.這種實作方式的思路很好理解:設定一個一間隔時間,例如50毫秒,以此時間為基準設定定時器,當第一次觸發事件到第二次觸發事件間隔小於50毫秒時,清除這個定時器,並設定一個新的定時器,以此類推,直到有一次事件觸發後50毫秒內沒有重複觸發。程式碼如下:
function debounce(method){ clearTimeout(method.timer); method.timer=setTimeout(function(){ method(); },50); }
這種設計方式有一個問題:本來應該多次觸發的事件,可能最終只會發生一次。具體來說,一個循序漸進的滾動事件,如果使用者滾動太快速,或者程式設定的函數節流間隔時間太長,那麼最終滾動事件會呈現為一個很突然的跳躍事件,中間過程都被節流截掉了。這個例子舉的有點誇張了,不過使用這種方式進行節流最終是會明顯感受到程式比不節流的時候“更突兀”,這對於用戶體驗是很差的。有一種彌補這種缺陷的設計想法。
#2.第二種實作方式的想法與第一種稍有差異:設定一個間隔時間,例如50毫秒,以此時間為基準穩定分隔事件觸發狀況,也就是說100毫秒內連續觸發多次事件,也只會依照50毫秒一次穩定分隔執行。程式碼如下:
var oldTime=new Date().getTime(); var delay=50; function throttle1(method){ var curTime=new Date().getTime(); if(curTime-oldTime>=delay){ oldTime=curTime; method(); } }
比起第一種方法,第二種方法也許會比第一種方法執行更多次(有時意味著更多次請求後台,即更多的流量),但是卻很好的解決了第一種方法清除中間過程的缺陷。因此在具體場景應根據情況擇優決定使用哪種方法。
#對於方法二,我們再提供另一個相同功能的寫法:
var timer=undefined,delay=50; function throttle2(method){ if(timer){ return ; } method(); timer=setTimeout(function(){ timer=undefined; },delay); }
最後說點外話,說明一下函數節流的名稱問題,大家往往會看到throttle和debounce兩個方法名,throttle可以翻譯為“節制,卡住”,debounce可以翻譯為“防反跳”。在《JavaScript高階程式設計》中作者介紹了方法一,作者使用了「throttle」這個函數名稱。而在《第三方JavaScript程式設計》書中同時出現了方法一和方法二,作者將方法一命名為“debounce”,將方法二命名為“throttle”。國內在同時介紹兩個方法的時候有些文章錯誤的將方法一命名為“throttle”,而將方法二命名為“debounce”,從英語的角度來說是很不負責任的。因此在這裡撥亂反正:方法一適合理解為“防反跳”,應命名為“debounce”;方法二適合理解為“函數節制”,應命名為“throttle”。
以上就是詳解JavaScript函數節流的內容,更多相關內容請關注PHP中文網(www.php.cn)!