本文共 9316 字,大约阅读时间需要 31 分钟。
================================================================================
Qomolangma OpenProject v1.0 类别 :Rich Web Client关键词 :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component, DOM,DTHML,CSS,JavaScript,JScript项目发起:aimingoo ()
项目团队:aimingoo, leon()有贡献者:JingYu()================================================================================ 一、NamedSystem 模块概要~~~~~~~~~~~~~~~~~~NamedSystem 是Qomo的可选载入模块。这个模块主要实现三个功能:
- 对$import()在路径识别上的增强 - Namespace 子系统的装载 - Alias 子系统的装载// TODO: NamedSystem.js是firefox兼容的。
二、NamedSystem 模块的构成与载入~~~~~~~~~~~~~~~~~~命名系统分成上述的三个部分,但它们的重要性并不相同。
$import()为了对路径识别进行增强,加入了一个标准的JavaScript对象:Url()。此后通
过一个匿名函数的执行来实现对$import()中功能的重述。$import()的重述,以及Url()对象的实现这两部分的功能,对于一般的系统来说都是必须的。接下来NamedSystem 模块将载入namespace和alias子系统的模块。但这两个模块是可选的。
在Qomo系统中,不强制使用命名空间或相关的功能。而且事实上,在绝大多数的情况下,Qomo的命名空间系统都是自维护的。NamedSystem模块的代码结构:----------Url = function() { // Url object的实现}();
void = function() { // $import()的重述}();
// 命名空间和别名子系统的载入$import('Namespace.js');$import('Alias.js');
// 命名空间和别名声明$import('Qomo.spc');$import('Qomo.alias');// more...----------
三、Url对象的分析~~~~~~~~~~~~~~~~~~在一般人看来,对一个Url的解析是很简单的。但如果你看一下注册表中这个键的子键:
[HKEY_CLASSES_ROOT/PROTOCOLS/Handler]你就不会觉得这是一件简单的事了。这个键描述了能在IE中支持的地址协议。IE扩展了Url,使用URI来统一描述资源文件、本机
和网络上的内容地址。使得浏览器跟资源管理器、操作系统紧密集成在一起。对于本机文件来说,你可以通过这样一种地址协议在IE中访问它(在IE中选“文件->打开”
菜单,选中该文件并确认之后,就可以在IE地址栏看见它了): file:///c:/windows/ntbtlog.txt你也可以在任何一个帮助文件(.CHM)上点鼠标右键,查看一个“属性”,就会得到这样一个
“URL”:mk:@MSITStore:C:/WINDOWS/Help/ups.chm::/MS-ITS:pwrmn.chm::/pwrmn_ups_overview.htm这此地址也要被Qomo的地址系统理解。因为他们都可以在IE里访问,也可以是HTML网页,当然
也就允许使用javascript和Qomo。Url()对象对协议的识别使用了一个正则表达式/^(/w+)(://*)([^//]*)/
这个表达式可以取得协议格式(type)和host地址,然后Url()中将分析整个Url,并返回在对象实例的属性中,这些属性表括:----------parse: function Url.parse() { [qomo_core code]}URL: type: httphost: sourceforge.netport: 80query: /project/showfiles.phppath: /listparam: group_id=157100&type=1params: [object Object]----------在上面这些属性中,parse()的属性显示是比较奇怪的。在javascript中,系统内置的函数作
为字符串显示的时候,源代码将被隐含。例如执行document.writeln(Array):----------function Array() { [native code]}----------Qomo中实现了这种“隐藏源代码”的效果。例如:
----------Url.parse.toString = $QomoCoreFunction('Url.parse');Url.toString = $QomoCoreFunction('Url');----------这样它们被显示出来的效果就类似于JavaScript系统的内置函数了。 在使用方面,Url()采用了于JavaScript内置对象RegExp()类似的设计。即可以将Url作为全局对象实例使用:----------Url.parse('http://blog.csdn.net/aimingoo/archive/2006/02/13/597658.aspx');for (i in Url) document.writeln(i, ': ', Url[i], '');----------
或将Url作为对象构造器使用:
----------url = new Url('http://blog.csdn.net/aimingoo/archive/2006/02/13/597658.aspx');for (i in url) document.writeln(i, ': ', url[i], '');
// url.parse('http://sourceforge.net/projects/qomo/');----------
四、$import()的重述~~~~~~~~~~~~~~~~~~在system.js的实现中,我们讲述到$import.get/set的实现是为了留备其它子系统对它
进行重述。而命名空间中单独处理了这一部分。1. URL BASE
~~~~~~ 通常情况下,我们会用document.URL或者window.location.href来取得当前网页的Url地址。但问题是,这种情况下得到的,可能会有参数,例如.aspx调用后面的参数表。这会给后面的分析带来麻烦。因此在Qomo中,采用了一种技巧来取得真实的BASE URL:---------- // url base for current document var BASE = function() { var el = document.createElement('IMG'); el.src = '.'; return el.getAttribute('src', 1); }();----------此后,Qomo创建了一个Url()对象,对BASE进行分析(parse),其中的query属性就 我们
所需要知道的:基于当前Host的绝对路径(docBase)。2. 暂存引用(reference)
~~~~~~ $import操作get/set方法,使得外部代码中可以通过$import.get()来取得$import()内部函数的引用。由于内核单元初始化结束后会调用$import.OnSysInitialized(),因此我们甚至可以暂存一个get/set方法的引用:---------- var $getter = $import.get; // 暂存get()方法的引用 var activeJS = $getter('activeJS'); // 暂存_sys.activeJS()的引用 // more...----------3. 添加_sys的内部属性
~~~~~~ 在重述后的$import()中需要更多的特性。这些特性(最好被)集中表现在_sys内部对象上。而$import.set()提供了这种可能性:---------- $import.set('docBase', docBase); $import.set('absBase', absBase); // more...----------这样一些新的属性就被添加到_sys对象上了。这使在其后的其它模块对$import()进行重述时可以访问docBase属性或absBase()方法。4. 被重述的特性
~~~~~~ 在NamedSystem模块中主要对transitionUrl()方法进行了重述。也就是说,NamedSystem重新理解了$import(targetUrl)中的targetUrl参数。使得它完整支持以下的特性: - targetUrl可以是基于system.js的相对路径. (仅在system.js单元) - targetUrl可以是基于当前host的绝对路径. (缺省行为) - targetUrl可以是基于当前.js的相对路径. (namesystem.js重述) - targetUrl可以是命名空间/别名下的包. (Namespace.js重述) 五、命名空间(namespace)子系统~~~~~~~~~~~~~~~~~~1. 什么是命名空间
~~~~~~ 至少是看起来,命名空间(系统)好象是一个了不起的系统。因为几乎现在的流行语言都要支持它。好象不支持它的话,就算不上流行,也不会流行起来一样。事实上,命名空间没有什么了不起。如果你只是想写一个类,或者一个可控制的类继承
树,那么你用不上命名空间。但如果你想整合几个不同的类库,或者一大堆的第三方组件包,那么这些组件包中总可能存在两个同名的类。这种情况下,你就需要将不同的类放在不同的命名空间里头,使得它们不相互冲突。于是,就需要就UI.Microsoft.Tree和UI.Yahoo.Tree这样的命名空间存在了。2. JavaScript中的命名空间
~~~~~~高级语言的命名空间支持这样的一种特性,例如:----------$import(UI.Microsoft.Tree);var aTree = new TDirectoryTree();----------
这种情况下,系统会默认认为你在创建一个UI.Microsoft.Tree.TDirectoryTree的树。也说是说,高级语言会将命名空间作为“作用域”的限定符来使用。而在JavaScript中,
作用域要么是函数内(或更内层),要么是函数外,你没有办法指定作用域在哪一个命名空间里。——在上面的这个例子里,JavaScript会认为是在创建一个TDirectoryTree的树。JavaScript v1.3中不存在命名空间。但在更高版本的JavaScript中,例如JScript 8(.net)
中,或者在JavaScript v2中,就存在命名空间。——事实上,在JavaScript 2的规范里,命名空间是类型,而且是第一类(first class)的。由于在JavaScript v1.x中不存在命名空间的概念,而且作用域限定是JS解释器内部理
解的,因而不可能改变。因此JavaScript 1.x中(通过第三方代码实现)的命名空间,通常只具有“扩展类继承树”的作用,而不具备作用域限定的作用。3. 如何实现命名空间
~~~~~~ 在JavaScript中实现一个(没有作用域特性的)命名空间是很简单的事。因为他事实上是一个类的全名而已。那么这种情况下,一段简单的实现代码可以是这样:----------// 1. 命名空间的建立var Qomo={};Qomo.System = {};Qomo.System.RTL = {};// 2. 类, 构造器function MemProf() { // Object constructor...}
// 3. 命名空间上的类Qomo.System.RTL.MemProf = MemProf;
// 4. 使用命名空间mem = new Qomo.System.RTL.MemProf();----------
可见,(JavaScript中,)一般意义上的命名空间,只是一个类构造器的引用而已。
4. Qomo中的命名空间
~~~~~~ 在Qomo中的命名空间除了上述的含义之外,还有另外一层意义,也就路径标识。例如我们如果要使用这样的代码----------$import('Qomo.System.RTL.*');// or$import(Qomo.System.RTL);----------那么我们真实的意图,是要将RTL中的全部文件载入。也就是“包载入”的功能。由于JavaScript不具有列(本地或远程)目录的能力,因为“包载入”需要一个描述包
内容的文件,例如package.xml。解析这个文件并逐一载入的功能并不复杂,但问题是Qomo中的$import()是基于路径系统的,因此需要将Qomo.System.RTL翻译成一个URL路径。这种工作,在一般的JavaScript实现的框架里,都是通过RegisterNamespace来实现的。
这个RegisterNamespace()可以实现为一个全局的函数,也可以实现为一个命名空间的方法,不一而足。但基本上的意思,就是将一个namespace与一个url path建立对照。Qomo也需要建立这样一个对照。但因为Qomo并不强制使用命名空间,因此Qomo也不强
制使用RegisterNamespace的方法。取而代之的是“影射(map)”系统。在$map()函数中,Qomo创建了一个私有、唯一的$map$对象:
----------var $map$ = { //mapper of all path //0..n : dynamic properties with this.insert()signpost : function(p) { ... }, remove : function(p) { ... }, insert : function(p, n) { ... }}----------
$map中会有一些0..n等数字为属性的,数字代表路径上长度。例如"/system/rtl/'长度
为12,那么他会在$map$[12]属性指向的对象中。这里利用了JavaScript的自动类型转换。事实上,我们是在使用$map$['12']这个属性。
这种使用方式看起来象是数组,但$map$比数组“干净”:没有一些多余的方法或者属性。这个技巧事实上是用path.length作为hash_key建立起了一个哈希表。接下来,$map$['12']存放的是一个用直接量方式声明的对象:
----------sp = { paths: new Array(), names: new Array()}----------首先我不认为在JavaScript中会创建一个“多么巨大”的命名空间系统,其次我认为在使用path.length作为hash_key之后,已经不会存在多少的hash碰撞了。因此在paths/names对照中,我简单的使用了数组。5. 路标
~~~~~~请注意上面的对象使用的变量名sp(signpost)。我使用“路标(signpost)”来说明这个map结点,有什么含义呢?在一个name <-> path的映射系统里,我们可以发现一个现象:
----------url1 = /Qomo/Component/Tree/NodeTree/url2 = /Qomo/Component/Tree/url3 = /Qomo/Component/$map(Qomo.Component, url3);$map(Qomo.Component.Tree, url2);$map(Qomo.Component.Tree.NodeTree, url1);---------
在这个对照中,我们发现其实没有必须存储全部的对照表,我们只存储Qomo.Component与url3的对照,然后我们可以根据一种简单的关系运算,就可以得到其它的子空间所对应的path了。这种空间的管理方式,我称为"路标(signpost)":
---------$map(Qomo.Component, url3);$mapx('Qomo.Component.Tree.NodeTree');---------与$map()并不一样,$mpax()并不需要路径参数($map()第二个参数)。$mapx()根据字符串
向前查找,当到达Qomo.Component时找到一个有效的、已存在的命名空间。这时取出它映射的路径url3。接下来就可以简单的建立出一个 name -> path 的对照:--------- Qomo.Component --> url3 Qomo.Component.Tree --> url3 + 'Tree/'; Qomo.Component.Tree.NodeTree --> url3 + 'Tree/NodeTree/';---------我们可以发现这种关系是可以计算出来的(因而不需要存储在$map$)。而关键在于,我们需要查找到“路标”:Qomo.Component。因此$map$事实上并不需要存储全部的 name <--> path的映射。它只需要存储上面这种
关键的“路标(signpost)”。这样做的另一个好处是,我们如果将一个命名空间的物理位置转移,那么我们也只需要改变它的路径,他的子空间和相关路径的关系并不需要改变。$map$.signpost()方法,用于通过路径(path)在$map$中查找一个最近的路标。事实上,
它返回该signpost上的namespace。——我不希望外部代码有机会改变$map$中的路标,如果你要这样做,请通过$map$.remove()和$map$.insert()方法。6. 命名空间到路径的运算
~~~~~~ 简单的说,“路标(signpost)”系统用于“路径到命名空间”的检索。那么反过来如何处理呢?前面我们说到过,命名空间是一个对象直接量。我们确定了命名空间的含义之后,我们
也应该知道,命名空间是一个独立的系统,与程序代码本身的逻辑无关。那么,我们也可以知道命名空间“对象”中的一些原生属性是没有实际意义的。例如constructor。我们这里要使用constructor属性的唯一原因,只是因为它不会在“for .. in”循环中
被列举出来。事实上象toString()这样的“对象基本方法”都不会被列举出来。但只有constructor在“不继承”的独立系统中没有确定的意义。因此在Qomo系统中,有很多独立的系统将会使用constructor来存储一些关键属性,这
只是为了达到属性名的隐藏,以避免与其它第三方系统在属性名上的命名冲突。这些“Qomo中独立的系统”包括命名空间、别名、多投事件和类方法。在命名空间中,我们利用namespace_object.constructor来存储它实际指向的路径。也
就是说,“命名空间到路径的运算”只是简单的存取constructor属性值。7. 小结
~~~~~~ 在命名空间中,可以用$map()来建立一个命名空间,并映射到一个URL路径。这种映射关系被作为一个路标保存在内部的$map$对象中。"路径->命名空间"运算通过$p2n()来实现,其本质是查找$map$中的路标。
"命名空间->路径"运算通过$n2p()来实现,其本质是存取命名空间对象的constructor属性。
命名空间可以是虚的。这种情况下,它的constructor指向一个空字符串。
命名空间是可以通过$mapx()来扩展得到的,这种情况下它不需要在$map$中保存路标。
(最重要的,)在JavaScript 1.x中实现的命名空间不具有作用域的意义,他本质上是一个
对象构造器的全称、限定符,以及路径信息的映射。 六、别名(alias)子系统~~~~~~~~~~~~~~~~~~在Qomo中有一个并不十分成熟的别名系统。尽管它是可用的,但在使用之前,你应该注意“对一个命名空间建立别名,并不会影响到子命名空间”。除了这种限制之外,这个别名系统还是很方便的。你可以看一下Alias.js的实现代码:简
单而又快捷。哈哈。别名事实上也是一个命名空间,只不过它的constructor指向另一个命名空间而已。关于这
种结构,在$n2p()的实现中已经做过处理,因此与原有的Namespace系统能很好的并存。因此(作为一个示例),Qomo.alias演示了一个简单的别名声明。
---------$alias('Qomo.RTL', Qomo.System.RTL);---------这将使得Qomo.RTL命名空间被创建,且可以作为Qomo.System.RTL的别名使用。但请留意,
这并不表明Qomo.RTL.MemProf也将是Qomo.System.RTL.MemProf的别名。——别名系统对子空间无效。这很大程度上降低了别名系统的价值。事实上,解决这个问题的方法很简单:
---------Qomo.RTL = Qomo.System.RTL;---------——使用引用的方式来创建别名系统就可以了。但这可能为Namespace系统中的name-path
关系的维护带来更多的麻烦。因此,我(暂时地)放弃了这种技术。而仅在Alias.js的注释里提及到了它。