JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)

i042416發表於2021-01-29

這是Jerry 2021年的第 11 篇文章,也是汪子熙公眾號總共第 282 篇原創文章。

Jerry之前的文章  SAP UI5 OData謠言粉碎機:極短時間內傳送兩個Odata request, 前一個會自動被cancel掉嗎,介紹過SAP成都研究院CRM Fiori開發團隊開發過的一個Live Search的場景。

使用者建立Opportunity,維護Account欄位,每輸入一個字元,都會觸發SAP UI5 Input控制元件的liveChange事件。在該事件的onAccountInputFieldChanged處理函式里,根據使用者輸入,傳送OData請求到後臺進行查詢。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


如果使用者輸入速度很快,則在短時間內,會有多個OData請求傳送到後臺,進而出現Jerry文章裡描述的OData請求被cancel的情況。

JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)



JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


最近Jerry做SAP Spartacus開發,遇到了同樣的場景。因此透過本文把自己最近所學總結一下,記錄下SAP UI5和Angular裡如何使用函式防抖(Debounce)和函式節流(Throttle)來避免短時間內觸發高頻次函式呼叫的情況出現。

為了便於講解,Jerry做了一個只包含一個Input控制元件的SAP UI5頁面。原始碼 地址.

在Input裡輸入字元,會觸發liveChange事件,將當前Input的最新內容,傳送到一個我自己開發的後臺服務去。該後臺服務什麼也不做,只是簡單將收到的內容返回給UI.


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


這個SAP UI5頁面裡的Input控制元件的liveChange事件處理如下:


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


從Chrome控制檯列印的輸出來看,我在一秒鐘之內,連續快速輸入了1234共4個字元,一共產生了4個傳送往後臺的請求。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


SAP UI5如何使用函式防抖(Debounce)來降低函式呼叫的頻次

函式防抖(Debounce),最早源於機械開關和繼電器的術語“去彈跳”,即將多個訊號合併為一個訊號。

想象一個大家現實生活中都會遇到的場景:進電梯。電梯都有一個自動關閉門的超時時間,假設為2秒。當電梯檢測到有人進入時,會重置這個2秒的計時器。如果下一個2秒之內,沒有新的乘客進入電梯,電梯門才會自動關上。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


電梯延遲關門這個場景,就是一個典型的函式防抖的現例項子。電梯關門的行為就是“函式”,透過電梯門的自動關閉超時時間,2秒,來延遲電梯門的關閉動作的執行,從而降低電梯門的關閉頻率,這就是“防抖”。

可以想象,如果電梯門的自動關閉沒有設定超時時間,而是檢測到沒有人進出之後,立即關閉,這樣會大大增加電梯門開合的頻率,既浪費能源,也不安全。這就好比Jerry本文開頭提到的例子:既然我短時間內輸入了字元1234,我期望在UI看到的,是後臺服務接收到1234後返回的結果。至於後臺如何對前三個請求,即字元1,字元12和字元123進行處理,我不再關心。

我們可以仿照電梯門關閉超時時間的設定,來給SAP UI5的函式呼叫實現防抖控制。

下圖debounce變數是一個函式構造器,本身是一個函式,接收另一個函式fn作為輸入引數,職責是透過閉包,將fn改造成一個具有防抖控制功能的新函式,該新函式透過第17行的return語句返回。

防抖時間間隔透過函式構造器另一個輸入引數delay指定。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


假設我們指定的防抖時間間隔為3000毫秒即3秒,如果3秒之內,debounce函式構造器返回的新函式被不斷呼叫,此時執行上圖程式碼第19行,呼叫clearTimeout重置計數器,此時原始函式fn不會得到執行。這個場景可以類比成:在電梯關門超時時間內,又有新的乘客進入,電梯超時計時器重置,電梯門不會關閉。

程式碼第20行,使用setTimeout重啟超時時間間隔為3秒的計數器,3秒過後,如果JavaScript任務佇列裡沒有其他待執行任務,則執行原始函式fn. 程式碼的第20行,好比電梯裝置重新開啟了3秒的超時定時器。

如果在等待的3秒之內,沒有新的函式呼叫觸發,則3秒過後,執行21行的原始函式fn;這好比電梯在3秒之內,始終沒有新的乘客進入,則 3秒過後,電梯門自動關閉。

debounce函式構造器的使用方式也很簡單。

程式碼第78行,將原始的sendRequest函式,以及3000毫秒的防抖時間間隔,傳入debounce構造器,返回一個兼有資料傳送功能和防抖功能的debounceVersion函式。在第85行原來呼叫sendRequest函式的位置,改為呼叫debounceVersion函式即可。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


函式防抖功能的測試:我在同一分鐘的第46秒,48秒,50秒,51秒四個時間點,分別輸入了1,2,3,4總共4個字元,但是在最後一次即51.996秒又過了3秒之後,才僅僅有一個請求傳送到後臺:這說明3秒的函式防抖間隔生效了:


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)



JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


SAP UI5如何使用函式節流(Throttle)來降低函式呼叫的頻次

上述函式防抖的實現存在一個問題,還是以電梯的例子來說明。

設想有一個空間無限的電梯,關門的超時時間為3秒。如果不斷的有新的乘客以小於3秒的時間間隔進入電梯,則電梯門永遠沒有機會關閉——即函式永遠得不到執行。

函式節流(Throttle)是另一種降低函式呼叫頻次的思路,同函式防抖的區別是,後者能保證在指定的節流間隔內,至少執行一次函式。

函式節流構造器的一個最簡單的實現版本:

被節流器改造後的函式每次觸發時,取一個當前系統時間戳,同前一次觸發時取的時間戳比較。如果二者的時間差,大於等於構造器的輸入引數delay即節流時間間隔,則進入第39行的else分支,觸發原始函式fn;否則說明節流時間間隔還未到達,使用第34行setTimeout,將原始函式fn,重新放入JavaScript事件佇列內,延遲執行:


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


函式節流版本的構造器使用方式,同函式防抖版本的構造器沒有差別:將原始函式sendRequest傳入構造器throttle,返回一個具有節流功能的新函式throttleVersion,在Input控制元件liveChange事件處理函式里,呼叫throttleVersion這個新函式即可。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


函式節流的測試結果:我設定的節流時間間隔為3秒,從Chrome控制檯列印輸出能觀察到,SAP UI5確實是大致以3秒的時間間隔,向後臺發起的資料請求。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


本文介紹的兩種函式防抖和函式節流的實現程式碼,僅僅考慮了最基本的情況,還有很多不完善的地方,有興趣的朋友可以在網路上搜尋,這方面的資料非常多,這裡不再贅述。

Jerry之前的分享提到過,Angular是響應式程式設計開發庫RxJS的重度使用者,後者提供了眾多功能強大的Operators,使得Angular開發人員不用重複造輪子,就能輕易實現出具有函式防抖和函式節流的場景。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


用Angular重新實現本文SAP UI5的Demo,總共程式碼只有44行:


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


從rxjs/operators工具庫中直接匯出debounceTime和throttleTime這兩個operators:


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


類似SAP UI5 Input控制元件的liveChange,Angular FormControl的valueChanges也給應用開發人員提供了編寫業務邏輯,響應使用者輸入的位置:後者的valueChanges資料型別是Observable,應用開發人員可以透過pipe呼叫,傳入RxJS各種功能強大的Operators,讓自己編寫的包含業務邏輯的事件響應函式,按照實際需求來觸發。


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


比如上圖第39行程式碼,語義是:繫結到jerryFormControl的input控制元件有valueChanges發生時,首先經過防抖器的處理。至於是否能夠滿足觸發valueChanges對應的事件處理函式的條件,由防抖器debounceTime的內部處理邏輯決定。

RxJS防抖器debounceTime的內部實現使用了setInterval,邏輯比Jerry本文介紹的debounce函式構造器複雜得多了,透過這些呼叫棧就能感受一二:


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


Jerry這個Angular Demo的函式節流(時間間隔設定為2秒)功能測試如下:我在7秒之內,勻速輸入1234567890abc,可以看到總共觸發了三個傳送到後臺的請求,請求間隔為2秒:


JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)



JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


希望本文能幫助大家對函式防抖和函式節流的概念有一個最粗淺的理解,感謝閱讀。

更多閱讀

(0)  SAP UI5應用開發人員瞭解UI5框架程式碼的意義

(1)  SAP UI5 module懶載入機制

(2)  SAP UI5 控制元件渲染機制

(3)  HTML原生事件 VS SAP UI5 Semantic事件

(4)  SAP UI5控制元件後設資料的後設資料實現

(5)  SAP UI5控制元件的例項資料修改和讀取邏輯

(6)  SAP UI5控制元件資料繫結的實現原理

(7)  SAP UI5控制元件資料繫結的三種模式:One Way, Two Way和OneTime實現原理比較

(8) SAP UI5控制元件ID的生成邏輯

(9) SAP UI5控制元件的多語言(國際化,Internationalization,i18n)支援的實現原理

(10) XML檢視裡的button控制元件

(11) button控制元件和它背後的DOM元素

(12)  SAP UI5 OData謠言粉碎機:極短時間內傳送兩個Odata request,前一個會自動被cancel掉嗎

(13)  漫談SAP產品裡頁面上的Checkbox設計與實現系列之一

更多Jerry的原創文章,盡在:"汪子熙":

JavaScript, ABAP和Scala裡的尾遞迴(Tail Recursion)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24475491/viewspace-2754418/,如需轉載,請註明出處,否則將追究法律責任。

相關文章