这是Jerry在2020年的第80篇文章,也是王子熙第262篇原创文章公众号。系列目录(0)SAPUI5应用开发者理解UI5框架代码含义(1)UI5模块懒加载机制(2)UI5控件渲染机制(3)HTML原生事件VSSAPUI5语义事件(本文)(4)UI5控件元数据实现细节(五)UI5控件实例数据实现细节(六)UI5控件数据绑定实现原理(七)UI5控件数据绑定的三种模式:OneWay、TwoWay和OneTime实现原理对比(八)生成UI5控件ID的逻辑(9)UI5控件多语言(Internationalization,i18n)支持的实现原理(10)XML视图中的Button控件(11)Button控件及其背后的DOM元素本文将要讨论的事件处理SAPUI5控件将围绕下图所示的差异进行全文说明。先用一个简单的例子来回顾一下HTML原生事件处理的原理。有这么一个简单的HTML页面,它使用了一个原生的HTML按钮标签,通过onclick="copyText"注册了一个名为copyText的事件处理器。点击按钮后,响应函数copyText将Field1的值复制到Field2。这个通过onclick注册的事件处理器可以直接在Chrome开发者工具中查看。除了onclick,调用浏览器原生的addEventListener方法也可以为DOM元素注册事件。在企业级Web应用中,DOM树的结构通常并不简单。例如,对于只包含一个简单按钮控件的SAPUI5应用程序,在页面呈现后会自动生成五个div标签:如果每个控件都需要响应一个事件,则使用onclick或addEventListener为其注册一个事件处理程序DOM元素,随着DOM事件处理程序数量的增加,Web应用程序的性能将会下降。因此,SAPUI5引入了另一个概念,即所谓的Semantic(语义)事件来完成UI5控件的事件注册和响应工作。使用Jerry的文章,一个SAPUI5学习的脚手架应用,没有任何后台API依赖,开发一个只包含sap.ui.commons.button的UI5应用:在上面的Elements标签页中,显示了NativeHTML代码后生成的呈现SAPUI5应用程序。上一篇文章深入研究SAPUI5框架代码系列之二:UI5控件的Renderer中我们已经介绍了按钮标签的生成逻辑。我们在Chrome开发者工具中使用与上一个原生HTML按钮示例相同的操作方法查看UI5应用程序中按钮的EventListeners,但一无所获。选中“Ancestors”前面的勾后,一下子显示了很多item:展开item中的click,发现SAPUI5在button标签的父节点,即div标签上注册了click事件其id为content,如下图所示。看UI5应用中sap.ui.commons.Button的事件注册代码:这里没有HTML原生事件click,而是一个包含属性名press和一个JavaScript函数作为入参的JavaScript对象,传入UI5Button的构造函数:当用户点击这个按钮时,应该触发一个名为click的事件。和我们这里注册的按下事件的handler函数有什么关系呢?答案可以在UI5按钮的实现源码中找到。切换到Chrome开发者工具的Sources选项卡,按快捷键Ctrl+O,输入button,选择第一个结果Button-dbg.js:这里可以看到press是button支持的事件,定义在Button-dbg.js李:下面这段代码的意思是,当UI5的按钮有点击事件时,如果处于启用可见状态,则触发一个Press事件(this.firePress()):因此,就是Button的实现这个onclick函数实现了事件点击到事件按下的映射任务。上面调试器中第168行的this.firePress调用是如何成功调用到UI5程序中为按下事件注册的handler函数的呢?还记得本系列上一篇《深入研究SAPUI5框架代码系列之一:UI5Module的懒加载机制》中介绍的一个知识点吗?SAPUI5运行时为所有Modules维护了一个注册表,将这些Modules的信息存储在键值对的数据结构中。key的数据类型为string,value类型为window.eval(),它将加载JavaScript文件的内容作为输入参数,执行后返回的JavaScript对象。类似的原理,SAPUI5中的每个控件都维护一个事件注册表mEventRegistry,键值对结构,键的数据类型为字符串,存储事件名称,值类型为数组,存储应用程序实现的事件。响应函数。下图是我的脚手架应用中按钮控件的事件注册表,里面只有一条记录,key是press,value是一个数组。其中唯一的元素是我在脚手架应用程序中实现的事件响应,其中包括对警报函数的调用。下图所示的逻辑是:(1)SAPUI5框架根据事件名称press,从第237行的控件事件注册表中取出存储其事件处理函数的数组;(2)遍历数组,在for循环中使用JavaScript函数原型提供的call方法调用这些响应函数完成事件响应:此时,新的问题产生了:事件注册表中什么时候是唯一的条目按钮控件的mEventRegistry填了吗?回想一下本系列第一篇介绍的SAPUI5控件的原型链:Button->Control->Element->ManagedObject->EventProvider->BaseObject。UI5应用中的这行语句:newsap.ui.commons.Button()会依次执行控件原型链上每个节点对应的构造函数。控件事件注册表mEventRegistry的填充操作发生在EventProvider节点的构造函数中:上图中的变量oValue就是我新建按钮实例时传入的按下事件的处理函数。在1192行,调用attachPress注册oValue指向的函数。attachPress函数最终调用了EventProvider的attachEvent方法,将键值对写入mEventRegistry:至此,还有最后一个问题没有得到解答:在本文开头展示的Chrome开发者工具中,按钮SAPUI5页面渲染后生成的label显示在EventListenersNoresponsefunctionwasonecolumn中。但是在父节点为content的div标签中,可以观察到点击事件下的response函数。Button父节点的div标签上的click方法和本文讨论了这么久的按钮事件注册表中的press事件是什么关系?当点击按钮时,查看调试器中显示的调用栈的最外层,发现SAPUI5的jquery-dbg.js响应HTML原生的点击事件,触发事件的对象确实是对象whoseidiscontent是div标签,不是button标签,从event.currentTarget的值可以确认。上图中调用栈中的绿线就是分隔线。绿线下方的代码处理了HTML原生的click事件click,同时完成了通过div将click事件传递给它的子节点和button标签的任务。绿线上方的Button.onclick,我们上面已经解释过了,通过this.firePress将click事件映射为一个press事件,SAPUI5中所有后续的事件处理都围绕这个press事件进行。据SAPUI5开发组组长AndreasKunz介绍,按钮按下事件称为语义事件。与HTML原生点击事件直接通过onclick或addEventListener在HTMLDOM元素上注册不同,Semantic事件的注册和调用是通过SAPUI5框架的JavaScript代码应用于SAPUI5实现的控件。处理和响应要轻很多,可以避免应用程序性能随着DOM树复杂度的增加而下降。引入Semantic事件后,UI5控件不再直接响应HTML原生事件,而是通过一个名为UIArea的实体接收用户触发的HTML原生事件,派发给UI5控件,UI5控件再映射成一一对应语义事件,并调用应用程序中实现的响应函数。这里的UIArea可以类比为设计模式中的Facade(外观)模式,为SAPUI5应用开发者屏蔽了底层事件映射的复杂性。上图中UIArea的详细描述记录在SAPUI5官方文档中。下图高亮部分解释了UIArea,也就是Jerry文章的内容。感兴趣的可以移步此链接继续阅读。如果把本文提到的Semanticevent换个名字,比如叫它virtualevent,很容易让人联想到Angular、Vue和React中引入的VirtualDOM(虚拟DOM)的概念。本质上,这些前端框架都是以增加框架实现的复杂度为代价,引入一个中间抽象层来减少在JavaScript层直接操作DOM层带来的性能开销。顺便说一句,AngularJS中控件注册的实现与SAPUI5的思路是一致的:同样没有采用直接向HTMLDOM元素注册事件处理函数的机制。下图是一个Angularjs应用。第22行的ng-click指令告诉Angularjs框架,超链接被点击后,会按照模型字段名进行排序。Angularjs框架是如何解析ng-click指令并完成事件注册的?Angularjs应用bootstrap阶段,框架遍历HTMLDOM树,递归调用compileNodes方法,对每一个包含ng命令的元素进行逐个解析:当包含ng-click="sortField='name'"的a标签是解析,调用Angular元素元素的on方法进行事件注册:查看on方法的实现代码,我们可以看到Angularjs并没有向DOM元素注册事件响应函数,而是在框架中维护了一个控件事件注册像SAPUI5的表this.$$listeners(SAPUI5的名字叫做mEventRegistry),使用键值对数据结构来存储事件名称和它们对应的事件响应函数。Angularjs应用中,调用事件响应函数时的调用栈截图:更详细的SAPUI5和Angularjs的事件处理机制对比,可以参考我的SAP社区博客:事件处理机制对比:SAPUI5与Angular本系列下一篇文章介绍的内容:UI5控件元数据实现详解。谢谢阅读。系列目录(0)SAPUI5应用开发者理解UI5框架代码含义(1)UI5模块懒加载机制(2)UI5控件渲染机制(3)HTML原生事件VSSAPUI5语义事件(本文)(4)UI5控件元数据实现细节(五)UI5控件实例数据实现细节(六)UI5控件数据绑定实现原理(七)UI5控件数据绑定的三种模式:OneWay、TwoWay和OneTime实现原理对比(八)生成UI5控件ID逻辑(9)UI5控件多语言(国际化、Internationalization、i18n)支持的实现原理(10)XML视图中的Button控件(11)Button控件及其背后的DOM元素更多Jerry原创文章都在这里:《王子熙》:
