深入学习SAP UI5框架代码系列之三:HTML原生事件 VS UI5 Semantic事件
這是Jerry 2020年的第80篇文章,也是汪子熙公眾號總共第262篇原創文章。
系列目錄
(0) SAP UI5應用開發人員了解UI5框架代碼的意義
(1) UI5 module懶加載機制
(2) UI5 控件渲染機制
(3) HTML原生事件 VS SAP UI5 Semantic事件(本文)
(4) UI5控件元數據實現細節
(5) UI5控件的實例數據實現細節
(6) UI5控件數據綁定的實現原理
(7) UI5控件數據綁定的三種模式:One Way,Two Way和OneTime實現原理比較
(8) UI5控件ID的生成邏輯
(9) UI5控件的多語言(國際化,Internationalization,i18n)支持的實現原理
(10) XML視圖里的button控件
(11) button控件和它背后的DOM元素
本文將討論SAP UI5控件的事件處理,全文會圍繞下圖表現出的差異來闡述。
首先用一個簡單的例子來回顧HTML原生事件處理原理。
有這樣一個簡單的HTML頁面,里面使用了一個HTML原生button標簽,通過οnclick="copyText"注冊了一個名為copyText的事件處理函數。
點擊按鈕之后,響應函數copyText將Field1的值拷貝到Field2去。這個通過onclick注冊的事件處理函數,在Chrome開發者工具里可以直接查看。
除了onclick之外,調用瀏覽器原生的addEventListener方法也能給DOM元素注冊事件。
在企業級web應用里,DOM樹的結構通常都不簡單。例如僅僅包含一個簡單button控件的SAP UI5應用,頁面渲染出來后也會自動生成5個div標簽:
如果每個需要響應事件的控件,都使用onclick或者addEventListener給DOM元素注冊一個事件處理函數,隨著DOM事件處理函數數量的增加,web應用的性能會降低。因此SAP UI5引入了另一種所謂Semantic(語義)事件的概念,來完成UI5控件的事件注冊和響應工作。
使用Jerry文章 一個用于SAP UI5學習的腳手架應用,沒有任何后臺API的依賴 提到的腳手架,開發一個只包含sap.ui.commons.button的UI5應用:
上圖的Elements標簽頁里,顯示的是SAP UI5應用渲染完畢后,生成的HTML原生代碼。里包含的button標簽的生成邏輯,我們已經在前一篇文章 深入學習SAP UI5框架代碼系列之二:UI5 控件的渲染器 里介紹過了。
我們采用與前一個原生HTML button例子同樣的操作方式,在Chrome開發者工具里檢查UI5應用里該button的Event Listeners,卻什么也沒發現。
選中"Ancestors"前面的勾之后,一下子顯示了很多條目出來:
展開條目中的click,發現SAP UI5把click事件注冊在button標簽的父節點,即id為content的div標簽上了,如下圖所示。
再看看UI5應用里sap.ui.commons.Button的事件注冊代碼:
這里并沒有出現HTML原生事件click的身影,而將一個包含了屬性名稱press,值為JavaScript函數的JavaScript對象,作為輸入參數,傳入了UI5 Button的構造函數里:
用戶點擊這個按鈕時,觸發的應該是名稱為click的事件,和我們在這里為press事件注冊的處理函數有什么關系?
在UI5 button的實現源代碼里能找到答案。切換到Chrome開發者工具的Sources標簽頁,快捷鍵Ctrl + O,輸入button,選擇第一個結果Button-dbg.js:
這里能看到,press作為button支持的事件,定義在Button-dbg.js里:
下面這段代碼的含義是,當UI5 button有click事件發生時,如果其本身處于enabled并且是visible狀態,則fire一個Press事件(this.firePress()):
因此,正是Button實現里的這個onclick函數,實現了從事件click映射到事件press的任務。
上圖調試器里168行的this.firePress調用,最終如何成功地調用到UI5程序里針對press事件注冊的處理函數的呢?
還記得這個系列的前一篇文章 深入學習SAP UI5框架代碼系列之一:UI5 Module的懶加載機制 里介紹的一個知識點嗎?
SAP UI5運行時為所有的Module維護了一個注冊表,以鍵值對的數據結構存儲了這些Module的信息,鍵的數據類型為string,值類型即window.eval()將加載好的JavaScript文件內容作為輸入參數,執行后返回的JavaScript對象。
類似的原理,SAP UI5里每個控件都維護了一個鍵值對結構的事件注冊表mEventRegistry, 鍵的數據類型string,存儲事件名稱,值類型為數組,里面存放了針對該事件,應用程序實現的響應函數。
下圖展示的是我腳手架應用里的button控件的事件注冊表,只包含一條記錄,鍵為press,值為一個數組,里面唯一的元素即我在腳手架應用里實現的包含了alert調用的事件響應函數。
下圖展示的邏輯是:
(1) SAP UI5框架從第237行的控件事件注冊表里,根據事件名稱press,取出存放其事件處理函數的數組;
(2) 遍歷該數組,在for循環里用JavaScript function原型提供的call方法,對這些響應函數進行調用,完成事件響應:
至此又引出了一個新的問題:button控件的事件注冊表mEventRegistry里的那唯一的條目,是何時填充進去的?
再回憶本系列第一篇文章里介紹的SAP UI5控件的原型鏈:
Button->Control->Element->ManagedObject->EventProvider->BaseObject.
UI5應用里這一行語句:
new sap.ui.commons.Button()
會依次執行控件原型鏈上每一個節點對應的構造函數??丶录员韒EventRegistry的填充操作,就發生在EventProvider這個節點的構造函數里:
上圖的變量oValue,就是我new一個button實例時傳入的press事件的處理函數。在第1192行代碼里,調用attachPress將oValue指向的函數進行注冊。函數attachPress最終調用EventProvider的attachEvent方法,將鍵值對寫入mEventRegistry:
至此有最后一個問題還未解答:本文開頭部分展示的Chrome開發者工具里,SAP UI5頁面渲染后生成的button標簽,在Event Listeners一欄里觀察不到任何響應函數。而在其父節點,id為content的div標簽里,在click事件下卻能觀察到響應函數。
Button父節點的div標簽上的click方法,和本文討論了這么長時間的button事件注冊表里的press事件,到底有何關系?
按鈕被點擊時,查看調試器里顯示的調用棧最外一層,發現SAP UI5的jquery-dbg.js, 響應的是HTML原生的click事件,且觸發該事件的對象的的確確是id為content的div標簽,而不是button標簽,這一點可以從event.currentTarget的值來確認。
以上圖調用棧中綠色的線為分隔,綠線下方的代碼,處理的是HTML原生的點擊事件click,同時完成了將click事件,經div投遞給其子節點,button標簽的任務。
綠線上方的Button.onclick, 前文我們已經闡述過,通過this.firePress將click事件映射成press事件,后續SAP UI5的所有事件處理,均圍繞這個press事件進行。
按照SAP UI5開發團隊大佬Andreas Kunz的介紹,button這種press事件稱為Semantic事件。同HTML原生的click事件直接通過onclick或addEventListener注冊在HTML DOM元素上不同,Semantic event的注冊和調用都是通過SAP UI5框架的JavaScript代碼施加在SAP UI5自行實現的控件上,比HTML原生的DOM事件處理和響應輕量得多,能避免隨著DOM樹復雜度的增加而造成的應用性能下降。
引入Semantic事件后,UI5控件不直接響應HTML原生事件,而是通過一個叫做UIArea的實體,來接收用戶觸發的HTML原生事件,并將其dispatch給UI5控件,后者再將其映射成一一對應的Semantic事件,并調用應用程序里實現的響應函數。這里的UIArea可以類比成設計模式里的Facade(外觀)模式,對SAP UI5的應用開發人員屏蔽了底層事件映射的復雜度。
上圖的UIArea的詳細描述,在SAP UI5官方文檔里有記載。
下圖高亮的一段對UIArea的闡述,展開來講就是Jerry本文的內容,大家感興趣的可以移步這個鏈接繼續閱讀。
如果把本文提到的Semantic事件換個叫法,比如稱其為虛擬事件,那么很容易聯想到Angular,Vue和React里引入的Virtual DOM(虛擬DOM)概念。從本質上說,這些前端框架都采取增加框架實現復雜度的代價,引入一個中間抽象層,來減少直接在JavaScript層操作DOM層造成的性能開銷。
順便說一句,AngularJS里的控件注冊實現,同SAP UI5思路一致:同樣未采取將事件處理函數直接注冊到HTML DOM元素上的機制。
下圖是一個Angularjs應用,第22行的ng-click指令,告訴Angularjs框架,超鏈接被點擊后,根據模型字段name,進行排序。
Angularjs框架如何解析這個ng-click指令,并完成事件注冊的?
在Angularjs應用bootstrap階段,框架會遍歷HTML DOM tree,遞歸調用compileNodes方法,逐一解析每一個包含了ng指令的元素:
當解析到包含了ng-click = "sortField = ‘name’"的a標簽時,調用Angular元素element的on方法,進行事件注冊:
查看on方法的實現代碼可知:Angularjs也并未將事件響應函數注冊到DOM元素上,而是同SAP UI5一樣,在框架內維護了一個控件事件注冊表,this.$$listeners(SAP UI5的名稱叫做mEventRegistry),采用鍵值對的數據結構,來存儲事件名稱和其對應的事件響應函數。
Angularjs應用里,事件響應函數被調用時的調用棧截圖:
關于SAP UI5和Angularjs的事件處理機制比較的更多細節,可以參考我的SAP社區博客:
Compare Event handling mechanism: SAPUI5 and Angular
本系列下一篇文章介紹的內容:UI5控件元數據實現細節。
感謝閱讀。
系列目錄
(0) SAP UI5應用開發人員了解UI5框架代碼的意義
(1) UI5 module懶加載機制
(2) UI5 控件渲染機制
(3) HTML原生事件 VS SAP UI5 Semantic事件(本文)
(4) UI5控件元數據實現細節
(5) UI5控件的實例數據實現細節
(6) UI5控件數據綁定的實現原理
(7) UI5控件數據綁定的三種模式:One Way,Two Way和OneTime實現原理比較
(8) UI5控件ID的生成邏輯
(9) UI5控件的多語言(國際化,Internationalization,i18n)支持的實現原理
(10) XML視圖里的button控件
(11) button控件和它背后的DOM元素
更多Jerry的原創文章,盡在:“汪子熙”:
總結
以上是生活随笔為你收集整理的深入学习SAP UI5框架代码系列之三:HTML原生事件 VS UI5 Semantic事件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何在SAP Spartacus的scs
- 下一篇: 14TB企业级硬盘跳楼价:800元、5年