• Qomolangma实现篇(二):命名空间和别名子系统的实现


    ================================================================================
    Qomolangma OpenProject v1.0


    类别    :Rich Web Client
    关键词  :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component,
              DOM,DTHML,CSS,JavaScript,JScript

    项目发起:aimingoo (aim@263.net)
    项目团队:aimingoo, leon(pfzhou@gmail.com)
    有贡献者:JingYu(zjy@cnpack.org)
    ================================================================================


    一、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:
    http://sourceforge.net:80/project/showfiles.php/list?group_id=157100&type=1
    type: http
    host: sourceforge.net
    port: 80
    query: /project/showfiles.php
    path: /list
    param: group_id=157100&type=1
    params: [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的注释
    里提及到了它。

  • 相关阅读:
    HTML Meta中添加X-UA-Compatible和IE=Edge,chrome=1有什么作用
    CSS+DIV定位分析(relative,absolute,static,fixed)
    Web中常用字体介绍
    CSS中强大的EM
    一线开发忙着实现,二线开发忙着变现
    Eclipse之父、《设计模式》作者、Junit作者之Erich Gamma
    著名软件工程师与作家、极限编程的创始者、JUnit作者之Kent Beck
    学习要构造反馈闭环
    技术人员也要全面发展
    2019第13周日
  • 原文地址:https://www.cnblogs.com/encounter/p/2188713.html
Copyright © 2020-2023  润新知