深入学习SAP UI5框架代码系列之一:UI5 Module的懒加载机制
这是Jerry 2020年的第78篇文章,也是汪子熙公众号总共第260篇原创文章。
本文是深入学习SAP 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学习的脚手架应用,没有任何后台API的依赖 介绍的脚手架应用,创建一个只包含一个Button控件的UI5应用:
浏览器里打开,总共触发了18个请求,网络传输流量1.1MB, 页面总共加载了5.1MB资源(见下图底部紫色矩形框所示)。
顺便说一说,为什么页面加载的资源尺寸(5.1 MB)会大于网络传输的数据量(1.1 MB)?
网上有一种说法,页面加载的资源,是通过网络加载的资源,以及从浏览器缓存读取的资源总和,因此会出现Chrome开发者工具里显示的页面加载的资源尺寸大于网络传输数据量的情况。
这种说法不完全正确。更准确的说,页面加载资源统计的是前端页面加载的所有资源,经过解压之后的原始大小。
如图,打开Chrome开发者工具的Use Large request rows选项, 就能显示出经过网络加载资源解压缩过后的原始大小,如下图所示:
以上说明来自Google官网:
https://developers.google.com/web/tools/chrome-devtools/network/reference#uncompressed
回到我们的UI5应用,Ctrl+Alt+Shift+P,勾上"Use Debug Sources", 让SAP UI5加载调试版本的库文件:
待button显示在页面之后,打开Chrome开发者工具Sources面板,能看到sap/ui文件夹下多出来一个commons文件夹:
回忆一下我们的脚手架应用的代码里,新建了一个命名空间sap.ui.commons下的Button控件实例:
因此运行时,SAP UI5对应的Button Module会被加载。Button-dbg.js负责Button的生命周期管理和事件响应,ButtonRenderer-dbg.js负责将Button实例渲染成原生的HTML代码。
切换到Network标签页,选择任意一个Button Module加载的网络请求,把鼠标hover到Initiator列上,在弹出窗口就能看到一个调用栈,从中就能观察到是index.html即Button控件的消费者,触发了这两个Button Module的加载。
在index.html里实例化Button控件的代码处设置断点,重新刷新应用。因为sap.ui.commons.Button并不是原生的HTML element,所以调试器执行到代码第11行并且单步执行后,会触发Button Module的加载:
这就是SAP UI5 Module的懒加载机制:如果该页面没有用到Button控件,则对应的Button Module永远不会被加载。
下图sap-ui-core-dbg.js第26384行就是Button Module的加载入口,注意注释里lazy stub for XXX的提示:
requireModule准备加载sap/ui/commons/button.js这个Module文件:
Module文件通过AJAX被加载后,SAP UI5得到的只是纯字符串文本,还无法直接用其创建button实例。SAP UI5会调用浏览器原生API, window.eval(), 将button.js文件的字符串内容传入该API,执行结果是一个JavaScript对象,也就是SAP UI5 Button Module的运行时实体。
SAP UI5运行时为所有的Module维护了一个注册表,以键值对的数据结构存储了这些Module的信息,键的数据类型为string,值类型即window.eval()将加载好的JavaScript文件内容作为输入参数,执行后返回的JavaScript对象。
Module的可能状态为一系列枚举值:INITIAL, LOADED, READY, FAILED, PRELOADED.
回到我的例子,因为我的代码触发了Button Module的第一次加载,所以代码第16487行,将Module的状态标注为INITIAL.
继续调试:
Line 16514: 将Button Module状态设置为LOADING.
Line 16517: 根据全局标志位window.sap-ui-loaddbg的值决定加载Button Module的普通版本还是调试版本。
Line 16520: 根据Module名称获得待加载Module的url.
Line 16525: 使用jQuery.AJAX加载Button-dbg.js.
因为该Module若不加载完成,则我们代码里的new sap.ui.commons.Button无法继续下去,因此这里的AJAX调用以同步模式进行( async = false ). 在其成功加载的回调函数里,将Module状态设置为LOADED, response变量包含的就是Button-dbg.js的文本内容。
Module状态为LOADED,说明其文本内容已经加载完成,可以交给16543行的execModule函数执行了(注意该函数上面的IF条件)。
代码第16612,如调试器所示:变量sScript包含的就是Button-dbg.js的文本内容,待window.eval()执行完毕后,Module的状态设置为READY:
new sap.ui.commons.button这行语句看似仅仅是一个简单的实例构造操作,背后却隐藏着SAP UI5控件设计的思路。
SAP UI5的注释写的很清楚:首先用工厂方法创建一个新的空Button实例oInstance,然后再使用消费者调用new sap.ui.commons.Button时传入的参数对oInstance进行enrich:
我们查看Button Module的源代码,发现通过JavaScript的原型继承,Button的prototype为Control:
查看SAP UI5官网上对sap.ui.core.Control的说明:
Rendering: 每个SAP UI5控件都有对应的Renderer,被RenderManager调用负责生成原生的HTML代码。
显示/隐藏,Busy Indicator,支持关联自定义的CSS样式类,注册浏览器事件。
Control的原型是Element:
Element是SAP UI5页面的基本元素,主要用于UI5的内部实现。
Element的原型是ManagedObject:
因为这条原型链过长,Jerry就不一一截图了,大家只需要记住结论:从Button控件出发,沿着它的原型链往上回溯,最后会到达BaseObject(相当于ABAP/Java里的Object).
Button->Control->Element->ManagedObject->EventProvider->BaseObject.
因此,在执行Button自己的构造函数之前,其原型链上每个节点的构造函数会依次执行一次:
本系列下一篇文章:UI5 控件渲染机制。
感谢阅读。