本文共 10850 字,大约阅读时间需要 36 分钟。
===============================================================================
Qomolangma OpenProject v2.0 类别 :Rich Web Client关键词 :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component, DOM,DTHML,CSS,JavaScript,JScript项目发起:aimingoo ()
项目团队:../../contributor.txt================================================================================注:有关本次发布的一些重要信息请参见:
一、Qomolangma 2.0 Beta1
============================Qomo的上次发布是在5个月前,此次的beta1主要包含对框架、兼容层和Builder系统的更新。有关界面组件库、图形库的发布,大约要到beta2或beta3才会提供。Beta1主要的更新包括:
- 修改了大量代码和builder工具,实现对IE5.0的完整兼容,兼容safari v2,部分兼容opera - 对兼容层代码做了大量修改, 并调整了兼容层在system.js中的装载顺序 - 新增载入第三方代码的$import2()函数 - 实现接口委托 - 实现类声明中的通用的特性get/setter - 更好的列举对象Enum()的实现,示例参考TestCase/T_Enum.html - 分析JavaScript源代码语法的工具ParseLite, 修改自Brendan Eich的Narcissus,性能极大优化 - 一些小的实用工具函数:IsClass(), IsObject(), IsInterface(),HasInterface()等等 - 高性能的模式替换对象Pattern(),示例参考TestCase/T_StringReplacePattern.html - 提供了一个正则表达式的性能分析工具:/Debug/TestCase/RegExpPerformanceTool.html - 其它的一些小的修正,以及代码注释上的增改下面对其中的一些较特别的更新做补充介绍。
二、函数$import2()============================$import2()是一个特殊的$imoprt()实现,它只适用于装载第三方代码的情况。beta1中它只在ParseLite的实现中被使用了,但以后的代码中将会有很多的地方用到它。它的函数描述为:------------------function $import2(src, prepare, patch, condition);- src: 装入的.js的url地址 - prepare: 在装入代码前面添加的代码,例如变量初始化等 - patch: 在装入代码尾部添加的代码, 用于向外部(例如Qomo)传出数据 - condition: 装入代码的条件, 不填写时为true------------------
其中src与condition参数与它们在$inline()和$import()中的用法是一致的。这里特别讲述一下prepare
与patch。其中,prepare是简单的脚本文本,我们一般用它来初始化一些变量,以避免第三方代码包中的变量泄露出来,与Qomo的全局变量冲突。例如:------------------// 第三方代码文本(文件名: test.js)value1 = 100;name = 'mySystem';------------------如果我们直接执行它,那么Qomo的全局变量中就会多出value1和name两个变量名。如果我们用下面的方法来装载:------------------$import2('test.js', 'var value1,name;', '');------------------那么就不会有问题了。但接下来,我们装载的test.js又能如何使用呢?比如这个test.js有一个类,或者对象,我们该如何来
使用它呢?这就需要用到patch这个参数了。不过在讲述它之前,先说明$import2()中对this引用的特殊理解。$import2()总是返回一个对象obj,如果this没有指向window,则使用this值作为obj;否则为obj新
建一个对象实例。因此,如果你试图传入一个对象并用它传出值,那么可以这样写:------------------var obj = {};$import2.call(obj, 'test.js', ...------------------这样的技巧可以用来修改对象的原型,例如:
------------------$import2.call(MyObject.prototype, 'test.js', ...------------------当然你也可以直接使用传出对象,而无需事先声明它。例如:
------------------var obj = $import2('test.js', ...------------------然而这个对象(或this引用)传入之后该如何使用呢?一方面你可以为patch参数传入一个字符串做代
码块,这个代码块中可以直接使用this。另一方面,你也可以为patch参数传入一个函数,该函数在执行时的第一个参数就是这个this引用。例如:------------------var obj = $import2('test.js', 'var value1,name;', function(_this) { _this.value = value1; _this.name = name;});------------------这样一来,我们就可以在外部系统中得到这个对象obj,它的value、name则来自于第三方代码。当然,
其它的对象方法或类都可以用这种技术来从第三方代码中获取。$import2()的一个实现实例,是Common/ParseLite.js。它从3rd/jsparse.js中获取了parse()和tokens()
两个方法。而这两个方法被用作ParserLite.prototype原型方法。这样一来,ParserLite()就既具有Qomo的类的特性,又在对Qomo完全无扰的情况下,使用了第三方代码。
三、通用的特性get/setter
============================通用get/setter其实是Qomo V1中就已经具备的一个特性,只是未被公开。这个特性适用于这样的情况:
某个类的许多特性的方法都基本一致,或者适合放在同一个函数中来实现,例如:------------------function MyObject() {this.setName = function(v) { if (!v) v = 'normal'; this.set(v); }
this.setValue = function(v) { if (!v) v = 'normal'; this.set(v); }
// ...}------------------
那么你可以用下面的代码来完成相同的功能:
------------------function MyObject() {this.setValue = this.setName = function(v) { if (!v) v = 'normal'; this.set(v); }
// ...}------------------
也就是让setValue()与setName()使用同一个特性存取函数。为了让你在代码中能够区分调用来自于哪一个方法,Qomo在调用这个特性存取函数时,会多传入一个
参数,因此你可以写出如下的代码:------------------function MyObject() {this.setValue = this.setName = function(v, n) { if (!v) v = 'normal'; this.set(v);
ExtObject['OnChange' + n](); }
// ...}------------------
这个例子中ExtObject是一个假设的外部对象。而在上面的特性存取函数被调用时,例如:------------------obj.set('Name', ' - MyName');obj.set('Value', ' - MyValue');------------------则下面的外部对象方法也将被调用:------------------ExtObject.OnChangeName();ExtObject.OnChangeValue();------------------当然,你也可以在这里传入参数v,或者用switch()语句来识别变量n并加以处理.最后,请注意一个细节,对于通常的set/getter来说,其声明方法是:
------------------this.getName = function() { ...this.setName = function(v) { ...------------------而对通用的特性get/setter来说,传入的特性名是追加在其后的:------------------this.getName = function(n) { ...this.setName = function(v,n) { ...------------------不过两种方法实现的读取器的使用方法完全一致:------------------obj.get(n);obj.set(n,v);------------------ 四、接口委托(Delegate)============================接口委托可能是本次(Qomo V2 Beta1)发布中最复杂的一项改动。在以前的文档中提及过它只是暂时未被实现,也指出了未被实现的原因:没有应用。在Qomo V2开发中,因为$import2的出现,我们遇到了一个问题:如果一个对象是由$import2()
装载的代码来实现的,而该对象又声明了接口,则该接口也必须要委托给第三方代码实现。例如下面的代码中:------------------// 接口声明IMyIntf = function() { this.doAction1 = this.doAction2 = Abstract;}// 类声明function MyObject() { Attribute(this, '3RdObject'); this.doAction1 = function() { this.get('3RdObject').doAction1.apply(this, arguments); }
this.doAction2 = function() { this.get('3RdObject').doAction2.apply(this, arguments); }
this.Create = function() { // 1. 导入第三方代码 // 2. 用第三方代码创建对象并置'3RdObject'特性 this.set('3RdObject', aObject); }}
// 类注册,并声明该类实现IMyIntf接口TMyObject = Class(TObject, 'MyObject', IMyIntf);------------------
在这个例子中,除了基本的框架之外,MyObject必须用重复的代码来实现doAction1, doAction2, ...
等等类似的方法。事实上这些重复的代码并没多少特别的意义。所以我们将该接口的实现委托出去呢?在新的Qomo版本中,使用下面的类声明过程可以做到了:
------------------// 类声明function MyObject() { Attribute(this, '3RdObject'); this.Create = function() { // 1. 导入第三方代码 // 2. 用第三方代码创建对象并置'3RdObject'特性 this.set('3RdObject', aObject); }function proxy(_this) { return _this.get('3RdObject'); }
Delegate(_cls().Create, [ [proxy, ['IMyIntf']] ]);}------------------Delegate()工具函数实现在Interface.js中,用于委托一个对象/类的接口,交由第三方实现。在这个
例子中,它被交由一个返回第三方对象的代理函数(proxy)——当然也可以在Delegate()中声明并使用函数直接量。除了使用代理函数之外,也可以简单的使用一个对象(或对象直接量)。例如:
------------------ var obj = { doAction1 : function() { ... }, doAction2 : function() { ... } }Delegate(_cls().Create, [ [obj, ['IMyIntf']] ]);------------------
Delegate函数声明为:
------------------Delegate(consigner, confer);------------------其中consigner是委托者,在本例中是_cls().Create,由于_cls()是一个在类构造周期中使用的特殊函数,用于返回类的一个引用(本例中是TMyObject),所以事实上委托者就是TMyObject.Create。而该函数事实上直接指向构造器MyObject,而当一个接口被注册到构造器时,其子类和实例都将继承该接口。这就与在Class()中声明该类实现IMyIntf接口的语义是一致的了。上面函数声明中的confer是一个份委托协议。该协议总是一个数组,结构如下:
------------------[ [proxy, [confer_item, ...]], [proxy, [confer_item, ...]], ...];------------------如前所述,其中proxy可以是代理对象或代理对象。而confer_item总是一个字符串,呈如下格式:------------------[<*|+|->]InterfaceName[.MethodName[:AliasName]]------------------字符串开始应当是一个修饰字符,如果缺省,则修饰字符为"+"。修饰字符的含义如下:
* 实现指定接口,或所有接口的所有方法 + 实现指定接口/接口的指定方法 - 不实现指定接口/接口的指定方法也就是一个包含和排除的规则集。这个修饰字符有两条优先规则: 1. 排除匹配(-)优先于包含匹配(+/*) 2. 指定匹配(接口.名称)优先于通用匹配( * )这样,就包含了所有的委托关系。不在委托关系中的接口和方法,则默认由consigner自己来实现。这个字符串其它的三个部分用于指定接口。包括:
InterfaceName : 接口名. 例如IInterface MethodName : 方法名. 例如IInterface.QueryInterface AliasName : 别名, 这是指实现者(proxy)中用来实现MethodName的名字.如果MethodName缺省,则表明实现整个接口(的全部方法);如果AliasName缺省,则表明代理与委托者使用相同的方法名。对于类和构造器来说,委托关系与接口注册一样,也是可以被继承的。也就是说,一旦接口被声明
委托,则子类和对象实例都使用该委托关系来实现接口。另外,委托关系也是可以被覆盖的,这种覆盖是confer_item的,也就是说可以可选地覆盖先前协议中的某些部分。下面的例子说明这种继承与覆盖的关系。------------------function MyObject() { Delegate(_cls().Create, [ [{ doAction1: function() { alert('doAction1') }, doAction2: function() { alert('doAction2') } }, ['IMyIntf']] ]);}function MyObjectEx(){ Delegate(_cls().Create, [ [{ doAction2: function() { alert('doAction2 - MyObjectEx') } }, ['IMyIntf.doAction2']] ]);}
TMyObject = Class(TObject, 'MyObject', IMyIntf);TMyObjectEx = Class(TMyObject, 'MyObjectEx');
// 测试代码obj1 = new MyObject();obj2 = new MyObjectEx();
intf1 = QueryInterface(obj1, IMyIntf);intf2 = QueryInterface(obj2, IMyIntf);
intf1.doAction2();intf2.doAction2();------------------
需要注意的是,接口被委托实现并不表明对象也具有该方法。以上例来讲,intf1.doAction2()调用
成功,并不表明obj1.doAction2()也能调用成功。Qomo没有自动实现该对象方法的能力。五、接口内部聚合(Aggregate)
============================Aggregate在Qomo V2中有一种新的实现方案。Aggregate(聚合)的概念,是将多个接口实现在同一个
目标中。它在Qomo的早期版本中被用于构造器或函数的内部,用于表明该构造器或函数内部实现了某个接口,而在它的外(方法)中不被表现出来。它的用法如下:------------------function MyObject() { // 声明聚合 var intfs = Aggregate(cls().Create, IMyIntf, IObject, ...);// 实现被聚合的各个接口 var intf1 = intfs.GetInterface(IMyIntf); intf1.doAction1 = ... intf1.doAction2 = ...
var intf2 = intfs.GetInterface(IObject); intf1.hasEvent = ... intf1.hasProperty = ... ...}
// 声明MyObject()注册了某些接口Interface.RegisterInterface(MyObject, IMyIntf, IObject, ...);------------------
在Qomo V2中,在我们实现了Delegate之后,我们发现聚合在实质上也可以理解为一种委托。对于上例
来讲,我们可以用同样的委托代码来实现:------------------function MyObject() { Delegate(_cls().Create, [ [{doAction1: ..., doAction2: ... }, ['IMyIntf']], [{hasEvent: ..., hasProperty: ...}, ['IObject']], ... ]);}------------------因此,我们重新实现了Aggregate()这个工具函数,在它的内部存在一个名为Interfaces()的构造器。我们将Aggregate()的关系委托给该构造器的实例,并注册到接口系统中。这种新的实现方案比早期的版本更加简单,但使用的方法仍然是一致——不必再重写以前的代码。新的Aggregate()的实现方案演示了Delegate()的灵活应用:我们对Aggregate参数构造了一个代理对
象,并与该代理对象(自动地)创建了一份协议。不过,为此我们也稍稍地扩展了一下Delegate(),使得confer_item中直接InterfaceHandle,也就是接口的内部句柄。因此,在不知道InterfaceName的情况下,在Delegate()中也可以将InterfaceName填为一个整数值(InterfaceHandle)。不过目前这只被用在Inerface.js内部,用于Aggregate的实现。 六、其它============================1. 关于IE5、safari和Opera的兼容
---------在Qomo V2中将全面兼容IE5.0,不过你必须使用Builer生成一个兼容它的版本,而不能直接在IE5.0中直接装载(未经编译的版本)和调试。在Qomo V2中也完全兼容safari v2,需要留意的是,对于safari的早期版本并不兼容。这是因为safari
从v2才开始支持Function.caller。是同样的原因,却导致我们不能完全兼容Opera:它不支持Function.caller。而我们经过讨论,也无法
在Opera上模拟出该效果,所以我们完成了除该项特性之外的全部兼容代码。然后,开始期待Opera的新版本…… 2. 关于parse分析的问题与应用---------Qomo V2的Common目录中新增了一个ParseLite.js,我们通过装载第三方项目Narcissus的代码,并为它简单地注册了一个Qomo类,从而实现了这个工具。不过,有两点要加以说明。其一,这是一个优化过的Narcissus项目,原来的Narcissus在处理稍大的代
码块时效率就迅速地级数下降,通过灵活地使用RegExp,Qomo避免了这些问题。以对150K源代码进行分析为例,Narcissus需要450秒,而Qomo的ParseLite只需要~~EN~~不到8秒。其二,ParseLite使用一个被裁剪过的Narcissus代码。没有execute部分,也去掉了一些扩展语法和异
常处理——ParseLite希望被分析的代码是没有语法错误。所以ParseLite适合于一些特殊的语法分析,例如分析一个代码块中有多少个函数,或者一个特定片断
中的某个符号是何种语义。尽管它最终也返回一个语法树,但这个语法树不能直接用于Execute——它少了一些东西。关于ParseLite的使用,请参考Framework/TestCase/T_ParserLite.html
3. 接口对特性提供直接支持---------这是一个非常好的特性。在以前我们曾经声明过这样的接口:------------------INamedEnumer = function() { this.getLength = this.items = this.names = Abstract;}------------------但它的实现却相当麻烦,因为Qomo对名为"getXXXXXXXXX"的方法有限制,因此你必须这样写代码:------------------function MyObject() { this.getLength = function() { // 这里的代码是为Attribute写的 }this.Create = function() { // 这里的代码才提供给接口, 并通过访问Attribute才得到值 this.getLength = function() { return this.get('Length'); } }}------------------
在Qomo V2中,这个过程变得非常简单。对于接口来说,在QueryInterface()时,名字以"get/set"开
始方法名,将会被映射到"对象.get()"或"对象.set()"。除非对象自己实现了getLength,或者根本没有get/set方法(例如不是Qomo对象)。因此,用户不需要为此多写任意一行代码。如下例:------------------IUserInfo = function() { this.getName = Abstract; this.getAge = Abstract;}function MyObject() { Attribute(this, 'Name', 'MyName'); Attribute(this, 'Age', 30);}TMyObject = Class(TObject, 'MyObject', IUserInfo);
var obj = new MyObjet();var intf = QueryInterface(obj, IUserInfo);
alert(intf.getName());alert(intf.getAge());------------------
Qomo V2.0 Beta 1下载----------
或从如下地址签出SVN:----------
转载地址:http://ilhbb.baihongyu.com/