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)
    项目团队:../../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()

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

  • 相关阅读:
    .NET Core HttpClient调用腾讯云对象存储Web API的"ERROR_CGI_PARAM_NO_SUCH_OP"问题
    .NET Core 控制台程序读 appsettings.json 、注依赖、配日志、设 IOptions
    腾讯云短信服务使用记录与.NET Core C#代码分享
    .net core中使用Type.GetType()从字符串获取类型遇到的问题
    阿里云不同账号之间相同地域的VPC网络互访
    ASP.NET Core 实现用户登录验证的最低配置
    体验 ASP.NET Core 中的多语言支持(Localization)
    .NET Core 2.0 单元测试中初识 IOptionsMonitor<T>
    体验 PHP under .NET Core
    docker swarm:执行 service update 过程中服务短暂不能访问的问题
  • 原文地址:https://www.cnblogs.com/encounter/p/2188695.html
Copyright © 2011-2022 走看看