zoukankan      html  css  js  c++  java
  • 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的注释
    里提及到了它。

  • 相关阅读:
    LeetCode 252. Meeting Rooms
    LeetCode 161. One Edit Distance
    LeetCode 156. Binary Tree Upside Down
    LeetCode 173. Binary Search Tree Iterator
    LeetCode 285. Inorder Successor in BST
    LeetCode 305. Number of Islands II
    LeetCode 272. Closest Binary Search Tree Value II
    LeetCode 270. Closest Binary Search Tree Value
    LeetCode 329. Longest Increasing Path in a Matrix
    LintCode Subtree
  • 原文地址:https://www.cnblogs.com/encounter/p/2188713.html
Copyright © 2011-2022 走看看