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)
    ================================================================================


    一、Qomolangma对JS基本特性的增强
    ~~~~~~~~~~~~~~~~~~

    为了实现更为丰富的OOP特性,Qomo增强了JavaScript的一些基础特性。这主要表现在:
      - 对JS基本类型系统(的方法)的增强
      - 支持多投事件

    这其中,对基本类型系统的增强,将严格恪守一条原则:不修改Object()对象原型。

    除了array.indexOf()、array.remove()、string.trim() 等常见的增强之外,Qomo有
    几项特性是与其它可能(可能)不一致的。这几项内容随后列一专题来讲述:
      - Array.prototype.insert
      - String.prototype.format
      - Function.prototype.toString

    此外,因为Qomo以后将提供与Altas相同的、基于vs.net的可视编辑特性,因此一些基
    本的特性扩展参考或者拷贝了Altas的代码。但这些代码目前只是留在了JSEnhance.js
    中而未被启用。你可以不关注它们。

    在Mozilla系列的浏览器环境中,提供了一个uneval()函数,这个函数用于序列化脚本
    对象,在今后的开发中很有价值。但它被放在了Compat/common_ie6.js中。这里也只提
    及它,而不分析它的实现。


    二、JSEnhance.js中部分增强特性
    ~~~~~~~~~~~~~~~~~~

    首先,请记住JSEnhance.js最主要的特性是“它可以脱离Qomo framework使用”。这个
    单元不依赖于Qomo的任何特性。它使用自然的、原始的JavaScript方法来扩展JS特性。
    因此它可以用于任何的Framework。

      Array.prototype.insert
      ----------
      Qomo中为array.insert()提供了更强大的能力,使得它可以向任意位置插入数组、单
    个或多个元素。这与一些其它的框架不同:它们通常只提供插入单个元素的能力。

      String.prototype.format
      ----------
      Qomo中的string.format()是参考Delphi实现的。因此你会到匹配符是“%s”和“%n”。
    这里的“s(大小写均可)”用于指代一个被替换元,而“n(0..n)”用于指代第n个替换元。

    由于在JS中没有明确的类型,因此没有"%d"之类的匹配符。

    作为习惯,我提供了一个全局的函数:format()。

    关于string.format()的使用,参见DOCUMENTs/TestCase/T_StringFormat.html。

      Function.prototype.toString
      ----------
      在JavaScript中,匿名函数(立即值)声明、函数对象构造、函数的标准语法声明等都
    可以声明一个有效的函数。但这些函数的toString()并不一致。为了解决对函数名的依
    赖性问题,并使得下面的语法总有确定的含义:
        function func() { /* ... */ }; 
        foo = eval(func.toString());

    Qomo复写了function.toString()。使得它总是返回一个匿名函数的字符串。如(上例):
        function () { /* ... */ };


    三、JSEnhance.js中的多投事件系统
    ~~~~~~~~~~~~~~~~~~

    首先,最重要的一点是:Qomo的多投事件系统对任何框架来说,是“完全透明”的!因
    此,它可以在其它任何框架中,象一个普通的事件函数(响应句柄)一样地加入被植入。
    事实上,Qomo的多投事件与Qomo OOP框架完全地脱离开,不利用任何的OOP特性、框架特
    性。——这种设计思路完整地体现了Qomo的目标与宗旨,以及,我们对OOP的认知。

    下面的代码展示Qomo中的多投事件系统的特性:
    ----------
    e = new MuEvent();

    document.writeln(typeof e, '<BR>');
    for (i in e)
      document.writeln(' - ', i, '<BR>');
    ----------

    输出结果:
    ----------
    function
     - add
     - addMethod
     - clear
     - reset
     - close
    ----------

    这表明“多投事件对象,实际上是一个函数”,它提供“add()等五个方法”。

    由于“多投事件对象是函数”,因此下面的代码是成立的:
    ----------
    func1 = func2 = function() { /* ... */ };

    function MyObject() {
      this.OnExec = new MuEvent();

      this.run = function() {
        // do somethings
        this.OnExec();
      }
    }

    var obj = new MyObject();
    obj.OnExec.add(func1);
    obj.OnExec.addMethod(window, func2);
    obj.run();
    ----------

    这个例子用最简的代码演示了多投事件对象的使用。你看到我们最终仍然要通
    过某种方式来使OnExec()被执行。只不过它被执行的时候,将同时触发func1和
    func2两种行为。


      1. add(), addMethod()
      ----------
      在多投事件对象的方法中,addMethod()第一个不容易理解的东西。但我们需
    要了解到:使用add()加入的func1,执行期拿到的this对象会是obj本身;而使
    用addMethod()加入的func2,执行期拿到的this对象将是window对象。

    这有什么意义呢?

    例如setTimeout()这样的函数在执行期只允许传入函数,而不能传入对象方法。
    这就使得定时执行一个对象方法的代码只能这样写:
    ----------
    function doTimer() {
      obj1.call();
      obj2.call();
    }
    setTimeout(doTimer, 1000);
    ----------

    在使用MuEvent()的情况下,上面的代码就可以很简单了:
    ----------
    var e = new MuEvent();
    e.addMethod(obj1, obj1.call);
    e.addMethod(obj2, obj2.call);

    setTimeout(e, 1000);
    ----------

    addMethod()在Atlas里被称为addAction()。这两者的含义是一致的。成熟的多
    投事件系统通常都会提供这种特性。


      2. clear()与reset()
      ----------
      Qomo提供clear()方法来清除与该多投事件对象绑定的“事件句柄列表”。而
    reset()则在清除之后再添加一个“事件句柄”。由于MuEvent对象也是函数,因
    此下面的代码也可以添加一个事件投送列表:
    ----------
    var e1 = new MuEvent();
    var e2 = new MuEvent();

    // ...
    // add somethings to e1

    e2.add(func1);
    e2.add(func2);
    e2.add(func3);

    // clear e1, and add a list(e2)
    e1.reset(e2);
    ----------


      3. close()
      ----------
      Qomo提供一种非常特殊的“关闭多投特性”的机制。——注意这在其它的框架
    上都没有实现。

    Qomo的多投事件对象是一个普通的函数,只不过它多了add()、addMethod()等等
    方法。如果我们清除掉这些方法,那么该对象的外在表现就与一个普通函数完全
    无异。这种情况下,一个第三方的框架根本无法识别这个“关闭多投特性的‘多
    投事件对象’”,而当成一个普通函数处理。

    因此Qomo的多投特性可以完全透明地嵌入一个第三方框架。甚至象DOM这样的浏览
    器基础系统。例如下例:
    ----------
    var loading = new MuEvent();

    loading.add(loadPicture1);
    loading.add(loadPicture2);
    loading.add(loadPicture3);
    // ...
    loading.add(loadPicture1000);

    loading.close();
    window.onload = loading;
    ----------

    这种情况下,浏览器的DOM框架完全感觉不到loading(作为一个函数)有什么不同。

    Qomo提供的close()特性的作用远不至此。事实上,close()特性真正的价值在于对
    系统设计层面的考量。例如我们做一个TLabledEdit对象,也就是将一个Lable与一
    个Edit绑在一起。那么我们发现,我们事实上对Lable.onclick的行为的理解,肯定
    是“选中Edit并置输入焦点”。这种行为特征在设计之初就被确定了,根本不应该
    被更改。——当然,如果你的设计就是要更改,那另论。

    而原始的TLable的设计中,TLable.onclick是一个公开的方法,并且是多投事件。
    那么即使我们写下下面的代码:
    ----------
    FLabled.onclick.addMethod(FEdit, FEdit.onclick);
    ----------

    在其后的、用户的代码中仍然可以改变FLabled.onclick的行为。例如add/clear()。

    这显然是这个TLabledEdit组件的原始设计者所不希望的。因此,在提供了close()
    特性的情况下,它就可以在上面的代码中这样写:
    ----------
    // 当创建结束调用
    this.DoCreate = function() {
      FLabled.onclick.addMethod(FEdit, FEdit.onclick);
      FLabled.onclick.close();
    }
    ----------

    这样就可以保证onclick()的特性不被变更。而且,如果FEdit.onclick被变量(例
    如add/reset),FLabled.onclick可以正常地感知到。


      4. 为什么不提供del()
      ----------
      Qomo的多投事件不提供del()特性。基于两个原因:
        - del()可能导致事件的激活顺序被破坏
        - del()需要执有内部“事件句柄列表”中的事件方法的引用,这破坏了封装性

    因此,(在目前的版本中,)作为一项框架设计层面上的考量,Qomo不提供del()。但
    是,由于atlas的多投事件有del()方法,因此在将来实现嵌入vs.net的代码时,Qomo
    是可能会提供del()方法的。


    四、多投事件系统的实现分析
    ~~~~~~~~~~~~~~~~~~

      1. 基本的多投事件系统
      ----------
      最基本的多投事件系统实现方法是这样:
    ----------
    function MuEvent() {
      // this is a new obj instance
      var all = this;
      all.length = 0;

      function add(foo) { /* ... */ }
      function addMethod(obj, foo) { /* ... */ }
      function clear() { /* ... */ }
      function reset(foo) { /* ... */ }
      function run() { /* ... */ }

      var e = function() { return run.call(this, arguments) }
      e.add = add;
      e.addMethod = addMethod;
      e.clear = clear;
      e.reset = reset;

      return e;
    }
    ----------

    这样实现的看起来很简单、自然。而且由new()关键字构造的对象实例this已经被
    内部变量all执有了一个引用,用以建立事件列表。避免了不必要的开销。看起来
    是不错的。——自然close()方法的实现也很容易,不成问题。

    但是这种情况下,我们对比多个事件对象,会发现一个不可接受的事实:
    ----------
    var e1 = new MuEvent();
    var e2 = new MuEvent();

    alert(e1.add === e2.add)
    ----------

    你会发现结果是false,也就是说:有多少个事件对象,就会有多少个add、clear
    方法。其开销极其巨大:n * 5。


      2. Qomo中多投事件系统的实现基础
      ----------
      在Qomo里,这一切被巧妙地避免了。我为每一个事件对象建立了一个handle。它
    是一个索引。
    ----------
      function _MuEvent() {
        // get a handle and init MuEvent Object
        var handle = all.length++;

        //...
    ----------

    为了让add()等方法成为“唯一实例”,我将它放在了_MuEvent()之外来实现。但
    这种情况下,对象执有的handle对add()方法就是不可见的了。因此我们还需要一
    种机制,来使对象可以向add()等方法暴露handle。这里,我们选用了valueOf()。

    对于函数(多投事件对象)ME来说,它的valueOf()的结果指向自身:ME。在大多数
    的情况下,这是没有意义的。因此我们这样来实现valueOf():
    ----------
    ME.valueOf = function() {
     return handle
    };
    ----------

    而在add()中,我们这样来使用valueOf():
    ----------
    var all2 = []; // all ME() object for recheck.

    function add(foo) {
      var i=this.valueOf(), e=all[i];
      if (e && e==all2[i]) {
        // add...
      }
    }
    ----------

    由于我们使用了第二个数组all2来复核,因此可以避免用户使用这样的代码来套
    取、破坏多投事件列表:
    ----------
    // if e1's handle is 10, and hide into a Object/System
    var e1 = new MuEvent(); 

    // 套取用的函数
    f = function(){};

    // 指定欲套取的句柄
    f.valueOf = function() { return 10 }

    // 重置(注意所有的多投事件对象的方法是相同的)
    f.clear = (new MuEvent()).clear;

    // 破解e1的事件列表(利用valueOf()返回10的特性)
    f.clear();
    ----------

    所以这样来看,加入数组all2[]来复核是必须的。


      3. “强壮”与“快”是两难的
      ----------
      但接下来,我们也发现这个“多投事件系统”是不“强壮”的。为什么呢?因为
    valueOf()仍然可以被外部代码改写。——这将导致依赖它来获取handle的add()
    等方法失效。事实上,由于我们重定义了valueOf()的含义,也使得Qomo与一些第
    三方的框架、系统中可能出现不兼容。

    Qomo应当是一个强壮的系统。由于valueOf()的存在,影响了强壮性,也使“透明”
    成为空话。

    我们回到前面这个all2[]。事实上,由于复核的必要,我们已经存放了一份所有对
    象的列表。因此,不通过handle来查找ME和事件列表对象,是可能的:
    ----------
    function add(foo) {
      var e = all.search(this);
      if (e) {
         // ...
      }
    }
    ----------

    在这个代码中,我们需要在all.search()其实被设计成一个算法,用于在all2[]中
    查找this对象(也就是ME()函数)。而search()返回的,则是“使用all2[]中this对象
    的索引”,在all[]中查找到的“投送事件列表”。——这个索引其实就是handle。

    这样,就不需要重写ME().valueOf()来公布handle了。但是,由于每次add()等操作
    都将查找all2[],使得系统会相对慢一些。——简单的说:强壮了,但慢了。

    所以整个MuEvent的实现代码是这样:
    ----------
    var MuEvent = function (fast) {

      var all = {
        length : 0,
        strong : !fast, // ^.^
        // ...
      }

      return _MuEvent;
    }(true);
    ----------

    简单地说,上面的一行代码真实的反映了:强壮就不快,快就不强壮。


      4. 真的不快吗?
      ----------
      简单地分析一下我们在使用事件系统时候的一些特点,我们会发现:
        - 事实上通常我们会成批地添加一个事件,或者一个对象的一组事件
        - 事实上相关的对象、事件总是被“在临近时间上”被处理的

    例如我们通常会在对象初始化的时候写这样的代码:
    ----------
    obj.onclick.add(foo1);
    obj.onclick.add(foo2);
    obj.onmouseout.add(foo3);
    ----------

    而在多投事件体系中,同一对象的OnXXXXX事件通常是连续被创建的,而且刚刚完成
    创建的事件对象可能会被赋以一些初值。简单的讲,这些使用习惯表现为:
        - 最近创建的事件总是可能会被很快操作到
        - 最近操作的事件附近的(同一对象的)事件总是可能会被很快操作到

    基于这两个原理。Qomo设计了一个在all2[]中查找事件对象的方法:总是从上一次
    添加或查找到的事件对象的附近,开始前向、后向检索。具体的算法参见all.search().

    加入检索算法使得add等行为的速度大大的加快。当然,这是基于开发人员的代码行
    为分析的,而真正的“在数组中检索对象”的方法的效率并没有办法提高。这是JS自
    身无可回避的问题。

    但是,如果引用hash或者使用对象属性名来检测,则必然要给ME()对象一个“可在外
    部访问的key/name值”。这又回到了前面提供handle的“fast方法”同样的问题上。
    因此这样的问题,是不必要再讨论的。

    Qomo的JSEnhance.js中,默认采用"fast = false"的配置,以提供一套强壮的系统。但
    如果你确信你的系统是封闭的、不会导致第三方的框架的影响的,那么你可以在JSEnhance
    中开启下面这个开关:
    ----------
    var MuEvent = function (fast) {
      // ...

      return _MuEvent;
    }(false);     // <-- here, set to true
    ----------

    最后,最重要的一点提示,是Qomo在这个多投事件系统上的效率牺牲,只会表现在add
    等方法的调用上。并不会对ME()事件的执行构成任何的影响。因为在代码上:
    ----------
    function _MuEvent() {
      // get a handle and init MuEvent Object
      var handle = all.last = all.length++;

      var ME = function() {
        if (all[handle].length > 0)  // <--- 直接使用handle
          return run.call(this, handle, arguments)
      }

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

    由于ME()的执行可以直接使用内部的handle变量,根本就不会调用all.search()。因此
    Qomo只是在“维护事件投送列表(add/reset等)”时有一些search()的性能开销。“执行
    投送事件”时,是性能最优化的。

  • 相关阅读:
    如何提交docker镜像到DockerHub
    【leetcode】200. Number of Islands
    【Java NIO】一文了解NIO
    【Java】同步阻塞式(BIO)TCP通信
    【剑指offer】9、斐波拉契数列
    SolidWorks242个使用技巧
    BR(BoomerangRobot)机器人项目
    Android学习笔记基于回调的事件处理
    Android学习笔记基于监听的事件处理
    Android学习笔记Log类输出日志信息
  • 原文地址:https://www.cnblogs.com/encounter/p/2188709.html
Copyright © 2011-2022 走看看