深入学习 SAP UI5 框架代码系列之二:UI5 Module 的懒加载机制
本文是深入学习 SAP UI5 框架代码系列的第二篇文章。
系列目录
SAP UI5 应用开发人员了解 UI5 框架代码的意义
UI5 module 懒加载机制
UI5 控件渲染机制
HTML 原生事件 VS SAP UI5 Semantic 事件
UI5 控件元数据实现细节
UI5 控件的实例数据实现细节
UI5 控件数据绑定的实现原理
UI5 控件数据绑定的三种模式:One Way,Two Way 和 OneTime 实现原理比较
UI5 控件 ID 的生成逻辑
UI5 控件的多语言(国际化,Internationalization,i18n)支持的实现原理
XML 视图里的 button 控件
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 Debu 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 控件渲染机制。
感谢阅读。
版权声明: 本文为 InfoQ 作者【Jerry Wang】的原创文章。
原文链接:【http://xie.infoq.cn/article/3ba025e0dd0e03c5c7b0519b4】。文章转载请联系作者。
评论