• 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)
    项目团队:../../Qomo_team.txt
    有贡献者:JingYu(zjy@cnpack.org)
    ================================================================================


    一、概述:Qomolangma中的框架库(v0.1)
    ~~~~~~~~~~~~~~~~~~

    在UI层方面,Qomo一直没有足够的进展,因此Qomo在beta 1之前公布的代码看起来就象是一个语言实验
    工程,而不象是一个面向应用的项目。

    其实Qomo的前身(WEUI)本身就是围绕UserInterface Library来提出的,因此WEUI的确有自己的UI层。此
    外,它也有完整的DB和Graphics层(及一个VML的实现)。但是Qomo对UI层提出的目标与WEUI并不一致,因
    此这直接导致了“Qomo需要一个新的UI库”的结果。

    Qomo在beta 2中包含部分UI、DB层的代码,但是并不推荐将它归为Qomo的一个组成部分并应用。——尽管
    这些的确可以在Qomo下运行得很好,开发人员可以从中得到很多的思想与技术实现。——事实上Qomo也从
    WEUI的UI和Graphics框架的基础框架上借鉴了一些东西。

    但这些都不是这一组文章要讨论的内容。

    因为在Qomo的beta 2之后,除了一些底层语言体系的修补之外,Qomo团队将开始有关框架库(并不是UI库)
    的开发工作。这些工作包括:
      - 公共框架类库: Framework/Classes.js, Framework/Common/*
      - 日志、调试、分析和单元测试框架:Framework/Debug/*
      - DOM、CSS兼容层框架:Components/Compat/*

    二、Qomo的基础库与基础类库
    ~~~~~~~~~~~~~~~~~~

    Qomo的基础库是一些工具函数,或者原生(native)的JavaScript类。它的地位与RTL中的JSEnhance.js
    是相同的,但基础库并不只是针对JavaScript进行增强。

    Qomo的基础类库建立自Qomo的OOP框架。也就是说,至少是继承自TObjecct的类。基础类库表现为一些工
    具类、容器类、全局(单例)类和一些其它抽象层次较低的类。

    Qomo的基础库与基础类库位于:
          Framework/Common/*
    它通过一个包文件载入:
          Framework/Classes.js


    三、基础库中的工具函数
    ~~~~~~~~~~~~~~~~~~

    基础库中的工具函数(目前)包括在:
       - 系统实用工具: Framework/Common/SysUtils.js  
       - 对象实用工具: Framework/Common/ObjUtils.js
       - 类型或数据结构定义及转换工具: Framework/Common/ConvUtils.js
    三个文件中。

    其中,ObjUtils.js和ConvUtils.js虽然包括在Qomo Beta2中,但事实上没有正式发布。——很多代码
    已经移除;部分代码未经测试也没有相关的说明。因此本文先不讲述它们。

    SysUtils.js中的目前只发布了4个工具函数。事实上他们在我以前的工程中大量使用过。下面做一些
    简单地介绍。

      1. createUniqueID()
      ----------
      该函数产生一个唯一的标识符。一般来说,他在当前的Web页(包括多帧)中都是不重复的。它基于一
    个随机数产生的算法规则:即使是在同一时刻调用两次随机数,也会因种子的变化而产生不同的值。因
    此拼接随机数和日期值通常会得到一个唯一的标识。——如果对随机数的算法的设定出了问题,则另论。


      2. createUniqueVar()
      ----------
      UniqueID可以作为标识,但无法作为全局变量使用。而本函数则用声明一个全局变量,并且返回该变
    量名。这个声明可以被delete删除以回收内存。例如:
    ------
    function myFunc() {
      // 创建并得到变量名
      var name = createUniqueVar();

      // 使用该变量
      eval('name').value = 'abcdefgh';
     
      // 删除该变量
      eval('delete ' + name);
    }
    ------

      3. isVariant(varName)
      ----------
      传入一个变量名,该函数会返回该变量名是否是一个变量。既可以是全局变量,也可以是局部变量。
    例如:
    ------
    function myFunc() {
      alert(isVariant('myFunc'));
    }
    ------

      4. defined(aVariant)
      ----------
      查看一个变量或值是否被声明过。例如:
    ------
    var all = [1,,3,4];
    function myFunc() {
      for (var i=0; i<all.length; i++) {
        if (defined(all[i])) {
          alert(all[i]);
        }
      }
    }
    ------


    四、基础库中的异常与断言
    ~~~~~~~~~~~~~~~~~~

      1. JavaScript中的异常基础
      ----------
      在JavaScript语言中,创建异常对象的方法是:
        e = new Error([number[, description]]);

    你可以在创建时或者创建后抛出异常:
    ------
      // 创建时抛出
      throw new Error(100, 'this is exception - 100.');
      // 创建后抛出
      var e = new Error(101, 'this is exceptio - 101.');
      throw e;
    ------

    按照JavaScript的语言规范,你可以在try中捕获到异常后再次抛出。例如:
    ------
      try {
        // ...
      }
      catch (e) {
        throw e;    // 再次抛出
      }
    ------


      2. Qomo基础库中的异常
      ----------
      我们发现标准JS中的异常很难管理。例如异常的编号,或者显示信息时大多使用直接声明
    的字符串。因此,Qomo约定一个异常可以用两个成员的数组表示。例如:
          EAccessInvaildClass = [8109, 'Class invaild: lost typeinfo!'];
    在第二个成员(字符串)中允许使用%s通配符。例如:
          EAttributeCantRead = [8112, 'The "%s" attribute can/'t read for %s.'];

    Qomo认为这是一个标准的异常记录/对象的结构。这种定义是非常取巧的:
      - 如果不使用Qomo的异常框架,那么标准JS中将会把数组转换成字符串,这时得到的信息
        是可以阅读的。
      - 如果使用Qomo异常框架,那么由于Qomo替换了Error()类,因此将生成更友好的信息。
      - 在JSEnhance.js中,Qomo再次替换了Error()类,这使得%s可以被处理,从而使动态的
        组织异常信息成为非常便利的事。

    在RTL/Error.js中,Qomo重写了Error()类。这使得Error()有以下构造形式:
      e = new Error();
      e = new Error(number);
      e = new Error(number, description);
      e = new Error(number, description, instanceObj);
      e = new Error(a_qomo_exp);
      e = new Error(a_qomo_exp, instanceObj);

    其中,instanceObj 表明一个关联到该异常的对象实例。但Qomo并不处理instanceObj, 只是
    通过异常对象来传递它。这样可以使try .. catch捕获到的异常对象有一个instanceObj属性,
    指向触发异常时送过来的一个“参考实例”。

    而a_qomo_exp表是一个按qomo的规则声明的异常数组(参阅前面的内容)。这样我们就可以用下
    面的代码来简单的触发一个异常:
      throw new Error(EAccessInvaildClass);

    如果系统未装载过Error.js,那么显示的信息将是:
    ---------------------------
    错误: 8109,Class invaild: lost typeinfo!
    ---------------------------

    如果系统已经装载过Error.js,那么显示的信息将是:
    ---------------------------
    错误: Class invaild: lost typeinfo!
    ---------------------------

    或者我们也可以这样使用带通配符的异常:
      throw new Error(EAttributeCantRead.concat('get', 'Enumerator'));

    如果我们装载过JSEnhance.js,那么显示的信息将是:
    ---------------------------
    错误: The "get" attribute can't read for Enumerator.
    ---------------------------

    这样,无论如何,我们都能给用户“相对友好”的错误信息。


      3. Qomo基础库中的断言
      ----------
      Qomo中的断言实现得非常简单。它其实就是一个Qomo的异常。如下:
    ---------------------------
    var
      EAssertFail = [8001, 'assert is failed./n/n%s'];

    $assert = function (isTrue, info) {
      if (!isTrue) throw new Error(EAssertFail.concat([info]));
    }
    ---------------------------
    由于Qomo有自己的异常实现,因此断言的显示将非常友好。


    五、基础库中的性能分析工具(Profiler)
    ~~~~~~~~~~~~~~~~~~

    Qomo在Debug.js单元中载入了一个Profiler工具,是分析代码性能的利器。它的一个
    使用示例是:
      Framework/Debug/TestCase/T_profiler.html


    Qomo中的profiler使用起来非常方便,也可以使用多组的profiler。系统中单独初始
    化了一个全局的$profilers,以方便使用。

      1. 核心结构
      ----------
      如果不考虑输出的效果,那么直接调用Qomo的profiler就可以得到它的核心结构和
    使用流程了:
    ---------------------------
    <!-- 载入Profiler类 -->
    <script src='Framework/Debug/Profilers.js'></script>

    <script>
    // 在用户代码中插入分析语句, 然后执行
    function myFunc() {
      $profilers('myFunc').begin()

      // your code ...

      $profilers('myFunc').end()
    }
    myFunc();

    // 输出分析结果
    document.writeln($profilers);
    </script>
    ---------------------------

    但是这样输入的东西根本没有办法看。因此,Qomo提供一组工具来辅助使用profiler。
    当然,你也可以定制它。——这个后面再讲。

     
      2. 基础Dbg.Utils的简单使用
      ----------
      使用Dbg.Utils.js是非常便捷的做法:
    ---------------------------
    <!-- 载入Profiler类和调试用工具单元 -->
    <script src='Framework/Debug/Profilers.js'></script>
    <script src='Framework/Debug/Dbg.Utils.js'></script>

    <script>
    // 在用户代码中插入分析语句, 然后执行
    // (同上, 略... )

    // 重写$debug()函数
    $debug.resetTo(function() {
      arguments.join = Array.prototype.join;
      document.writeln(arguments.join(''));
    });

    // 显示profiler信息
    showProfiler($profilers);
    </script>
    ---------------------------

    注意这里有两处关键的细节。一是要求载入Dbg.Utils.js,至于它位于Profilers.js之前或者
    之后并没有关系。


      3. 为分析对象指定精确的标签
      ----------
      在开始的示例里,我们用一对
    ---------------------------
      $profilers('myFunc').begin()
      $profilers('myFunc').end()
    ---------------------------

    来开始和结束分析。这里的myFunc可以是任意字符,也可以是任意多的参数。这样,你可以
    指定:
    ---------------------------
      $profilers('myFunc', 'build').begin();
      $profilers('myFunc', 'build').end();

      $profilers('myFunc', 'execute').begin();
      $profilers('myFunc', 'execute').end();
    ---------------------------

    这样对一个函数的多组分析。也可以指定:
    ---------------------------
      $profilers('myFunc', '1').begin();
      $profilers('myFunc', '1').end();

      $profilers('myFunc', '2').begin();
      $profilers('myFunc', '2').end();
    ---------------------------

    这样来表明步骤。


      4. 处理递归
      ----------
    接下来,对于递归的函数,你可以采取两种方法来处理它。其一是加标签,例如:
    ---------------------------
    function calc(n) {
      $profilers('calc', n).begin();
     
      var v = n;
      if (v > 0) {
        v = n + calc(n - 1);
      }

      $profilers('calc', n).end();

      return v;
    }

    calc(10);
    ---------------------------

    第二种方法,则是借用profiler返回标志,例如:
    ---------------------------
    function calc(n) {
      var tag = $profilers('calc').begin();

      var v = n;
      if (v > 0) {
        v = n + calc(n - 1);
      }

      $profilers('calc').end(tag);

      return v;
    }

    calc(10);
    ---------------------------

    这两种方法中,第一种分另产生不同的profiler记录项,因此采用标准方法处理即可;第二种
    则产生同一记录项的多个记录值,对于这种情况,在Dbg.Utils.js中的showProfiler()展示了
    如何处理。


      5. 显示结果: $debug()的重写
      ----------
      $debug()最初被声明在system.js中,用于直接向document输出调试信息。但是这样必然会
    破坏网页的显示效果。——尤其profiler的信息量非常大。

      因此,在Dbg.Utils.js中,我们重写了它。使它的输出被放到缓存中:
    ---------------------------
    $debug = function() {
      // ...

      arguments.callee['$cached$'] += arguments.join('');
    }
    ---------------------------

    其中,arguments.callee直接向$debug()函数本身。使用callee而不是$debug自身,是避免
    $debug()被再次重写。

    我们还为$debug添加了一个方法resetTo()。这个方法传入一个函数,新函数要能够输出这些
    被缓存的信息。——它在被resetTo()时将被自动调用一次。然后该新函数将替代原$debug()
    在系统中的作用。

    例如我们在面第二节中讲到的:
    ---------------------------
    // 重写$debug()函数
    $debug.resetTo(function() {
      arguments.join = Array.prototype.join;
      document.writeln(arguments.join(''));
    });

    showProfiler($profilers);
    ---------------------------

    这个重写就是用于向document输出,它也被用在下面这个示例里:
      Framework/Debug/TestCase/T_profiler.html

    另外一处使用,则是在:
      Components/QomoHierarchyPoster.html
    它的写法是将信息显示到一个HTML元素中:
    ---------------------------
    $debug.resetTo(function() {
      arguments.join = Array.prototype.join;
      document.getElementById('Qo_DBGINFO').insertAdjacentHTML(
        'beforeEnd', arguments.join(''));
    });
    ---------------------------


      6. 显示结果: showProfiler()及其定制
      ----------
      事实上,Dbg.Utils.js作为一个实用工具单元,可以完全不用载入到当前页面。这种情况下,
    Profilers的功能依然是可用的。包括使用:
    ---------------------------
      $profilers('your_tag').begin();
      $profilers('your_tag').end();
    ---------------------------

    这样的方法来记录profiler数据。因为$profilers这个全局对象是被创建在Profiles.js中的。

    如果你不打算使用Dbg.Utils.js中的showProfiers()来显示结果。那么你可以自己写一个,例
    如打开一个新窗口来显示。这时你只需要参考一下showProfiers()的代码即可:
    ---------------------------
    function showProfiers(prof) {
      var data = prof.toData();

      // ...
    }
    ---------------------------

    这样从prof中得到的数据是如下的一种结构:
    ---------------------------
    a_data_instance = {
      'your_tag_1': [beginTime1, endTime1, beginTime2, endTime2, ...],
      'your_tag_2': [...],
      'your_tag_3': [...]
      // ...
    }
    ---------------------------
    你只需要写代码去循环处理即可。——注意beginTime/endTime的值是Number类型的。如果你
    要转换为Date()对象,那么可以“new Date(beginTime)”这样即可。

      7. 在系统中处理多个Profilers()对象
      ----------
      只要你愿意,你可以创建多个Profilers()对象,他们之间是没有干扰的。——当然,你也
    可以只创建一个,并用不同的标签来分组显示它们。这一切只取决于你的选择。

    如果你打算创建多个Profilers,那么大概的代码如下:
    ---------------------------
    var prof1 = new Profilers();
    var prof2 = new Profilers();

    // prof1('tag').begin() ...
    // ...

    showProfiler(prof1);
    showProfiler(prof2);
    ---------------------------

      8. 清理profilers数据
      ----------
      如果你要开始新一批的profilers分析。那么你应该清除一下原有的数据。但到目前为止,
    Profilers()对象并没有提供clear()方法。——我认为不必须。

    因此如果你需要清理数据,最好的方法就是重新创建一个。例如:
    ---------------------------
    // 重新创建全局的分析器
    $profilers = new Profilers();
    ---------------------------


    六、Profilers与AOP的结合使用
    ~~~~~~~~~~~~~~~~~~
    大概要为每一个函数去写.begin()和.end()会是一件让人痛苦的事,而且频繁地改动原先的
    函数,也不是是一件什么好事。因此事实上在Profilers的使用示例中,我采用的都是AOP来
    实现。在
      Components/QomoHierarchyPoster.html
      Framework/Debug/TestCase/T_profiler.html
    这两个文件中,都可以看到AOP的用法。

    其中,T_profiler.html载入了一个AOP_MyProf.js文件,这里的示例最为简单:
    ---------------------------
    // 加入profiler相关的代码($profilers是全局对象)
    var asp_import = new FunctionAspect($import, '$import', 'Function');

    asp_import.OnBefore.add(function(o, n, p, a) {
      with ($profilers(n, FN(a[0]))) {
        set('url', a[0]);
        a['$tag$'] = begin();
      }
    });

    asp_import.OnAfter.add(function(o, n, p, a, v) {
      $profilers(n, FN(a[0])).end(a['$tag$']);
    });
    ---------------------------

    我们看到asp_import就是一个为"$import()"创建的一个切面,这个切面的名字,就是"$import"。
    所以我们在OnBefore和OnAfter中看到的参数n,值就是"$import"。

    接下来,我们在OnBefore的事件处理函数中,添加了对每一次$import()调用的profiler分析。这
    里传入的参数a,就是调用$import()时的arguments。所以a[0]就是$import()的文件名。——因为,
    我们总是用“$import('a_js_url')”来导入文件的。

    调用$profilers()时传入了两个标签。其中n总是"$import",而FN(a[0])则是取到URL未尾的文件名。
    因此,相当于创建了一个名为"$import/a_js_filename"形式的标识。这在后面用showProfilers()
    时就可以看到了。

    接下来,由于我们还需要在showProfilers()时能显示一点数据,例如载入的实例的URL的完整径,
    因此我们调用了set()方法。这段代码实际相当于:
    ---------------------------
    asp_import.OnBefore.add(function(o, n, p, a) {
      var prof = $profilers(n, FN(a[0]));

      prof.set('url', a[0]);
      a['$tag$'] = prof.begin();
    });
    ---------------------------

    prof.set('url', ...)这行代码可以为一个prof添加任意多的、任何名称的定制数据。这些数据
    可以提供给showProfiler()来使用。——当然,你自己也可以写一个showProfiler()来处理这些
    数据。

    最后一行是为了处理在Qomo中将同一个.js文件导入多次。——"$import/a_js_filename"会重复,
    从而看起来象是处理同一段代码(像对递归过程profiler)。——因此这里把prof.begin()返回的
    标识放在a['$tag$']里。

    这里用了一个取巧的方法:AOP事件中的参数a是调用被关注点时的参数arguments,因此对同一个
    关注对象来说,OnBefore与OnAfter所使用的arguments也是同一个。所以当切面到达OnAfter时,
    我们只需要处理成
    ---------------------------
      $profilers(n, FN(a[0])).end(a['$tag$']);
    ---------------------------

    就可以了。——a['$tag']就是我们需要使用的begin()返回值。而且,我们也不需要去delete这
    个属性。因为当函数运行结束后,arguments将自动被javascript引擎销毁。

    同样的技术也被用在
      Components/QomoHierarchyPoster.html
    这个文件中。不过QomoHierarchyPoster.html在profilers中值得言讲的,却在于它对combine()
    的活用。

    在QomoHierarchyPoster.html中,我们处理了五个切面,并试图对它们做性能分析:
    ---------------------------
    var asp_getTopoString = new FunctionAspect(getTopoString, 'getTopoString', 'Function');

    var asp_LinesString = new FunctionAspect(getLinesString, 'getLinesString', 'Function');
    var asp_drawTopo = new FunctionAspect(drawTopo, 'drawTopo', 'Function');
    var asp_active = new FunctionAspect(activeTopoInCanvas, 'activeTopoInCanvas', 'Function');
    var asp_cache = new FunctionAspect(cacheTargetByNodes, 'cacheTargetByNodes', 'Function');
    ---------------------------

    而这时,我们只为一个切面的OnBefore()和OnAfter()添加了事件处理句柄:
    ---------------------------
    asp_getTopoString.OnBefore.add(function(o, n, p, a) {
      a["$tag$"] = $profilers('$prof', p, n).begin();
    });

    asp_getTopoString.OnAfter.add(function(o, n, p, a, v) {
      $profilers('$prof', p, n).end(a["$tag$"]);
    });
    ---------------------------

    为了让其它几个切面也得到相同的处理能力,我们使用了Qomo AOP中的“联合(combine)”,这
    非常简单,一行代码就可以了:
    ---------------------------
    asp_getTopoString.combine(asp_LinesString, asp_drawTopo, asp_active, asp_cache);
    ---------------------------

    于是,上面的五个切面所处理的被观察者(函数)执行时,都会触发asp_getTopoString的OnBefore
    和OnAfter事件了。

    我相信Qomo AOP的价值远非如此。接下来的应用,看大家的吧。^.^


    七、其它
    ~~~~~~~~~~~~~~~~~~

      1. 可以独立于Qomo框架的内核模块
      ----------
      在Qomo内核中,Profilers.js、Dbg.Utils.js与JSEnhance.js一样,都不需要加载Qomo的
    框架,因此可以直接在其它的、第三方的代码框架中使用。这样的内核模块还包括:
    ------------
    // 包括Profilers, Dbg.Utils和RepImport等
    $import('Debug/Debug.js');

    // 替换错误处理
    $import('RTL/Error.js');

    // 接口
    $import('RTL/Interface.js');

    // 协议(目前只包括URL分析)
    $import('RTL/Protocol.js');

    // 兼容层
    $import('Compat/CompatLayer.js');

    // 命名空间(依赖于system.js与Protocol.js)
    $import('Names/NamedSystem.js');

    // 脚本功能增强
    $import('RTL/JSEnhance.js');
    ------------

    由于Profilers被设计成可以针对整个Qomo做性能分析(包括最初的$import(),以及后续加载
    的各个组件与类),因此它必须在最先载入。同样的原因,整个的Debug.js是第一个由system.js
    载入的单元。


      2. 框架库之其它内容
      ----------
      在Qomo beta2的基础库中,还包括三个重要的成员:
      - 队列/池,以及处理器类
      - 时间序列、时间线与数据发生器
      - ajax原型:HttpMachine()

    这些内容在随后我将专门撰文讲解。

  • 相关阅读:
    HDU 2955 Robberies(01背包)
    HDU 2602 Bone Collector(01背包)
    HUST 1352 Repetitions of Substrings(字符串)
    HUST 1358 Uiwurerirexb jeqvad(模拟解密)
    HUST 1404 Hamming Distance(字符串)
    HDU 4520 小Q系列故事――最佳裁判(STL)
    HDU 2058 The sum problem(枚举)
    【破解】修改程序版权、添加弹窗
    HDU 1407 测试你是否和LTC水平一样高(枚举)
    HDU 1050 Moving Tables(贪心)
  • 原文地址:https://www.cnblogs.com/encounter/p/2188695.html
Copyright © 2020-2023  润新知