zoukankan      html  css  js  c++  java
  • Qomolangma实现篇(六):Qomo的OOP框架的实现技术

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

    (* 注:本文讲述的是FT4的一个修正版本中的OOP实现,而非原始发布版本。
    原始版本下载地址:http://www.01cn.net/users/aimingoo/files/Qomo.V1(f4).zip
    修正版本下载地址:http://www.delphibbs.com/keylife/images/u40/Qomo.V1(f4).p1.zip

    请对原版中的文件对行替换以完成修正升级
    )


    一、Qomolangma中类继承的基本架构
    ~~~~~~~~~~~~~~~~~~

    Qomo在Object.js中,通过实现Class(),封装了“类继承”体系的绝大部分细节。如果你仅
    是要使用Qomo,那么你只需要通过“实现篇(五)”去了解一些基本语法就可以了。但是如果
    你想了解一些更细节的内容,或者你想让自己具有控制Qomo框架的能力,那么你应该继续将
    这篇技术文档读下去。

    ——尽管,这并不如你想象地那样容易。

    Qomo的加入,使JavaScript具有了完整的“类继承”体系。类继承的出现,使JavaScript有
    能力处理更复杂的OOP语法和语义。而且能够通过更好的继承体系,来为JavaScript提供一
    些框架一级的语言特性,例如Interface与SOA。而这些能力,都会通过一个Class()关键字来
    实现。它内部实现的框架代码如下:
    ----------
    Class = function() {
      // ...
      // 基本的类数据、公共变量等

      // 构建类数据块
      function ClassDataBlock() {
        var cls = function (Constructor) {
          // ...
        }
        return cls;
      }

      // 真正的 Class() 函数
      function _Class(Parent, Name) {
        var cls = new ClassDataBlock(Parent, Name);
        cls.OnClassInitializtion(Constructor);
       
        // 构建对象(实例)数据块
        function InstanceDataBlock() {
          // ..
        }
       
        // 真正的对象构造器函数
        cls.Create = function () {
            // ...
     var Data = new InstanceDataBlock();
            this.get = Data.get;
            this.set = Data.set;
            this.inherited = Data.inherited;
            // ...

     if (this.Create) this.Create.apply(this, arguments);
            // ...
        }
        cls(Constructor);
        cls.OnClassInitialized(InstanceDataBlock);
        // ...

        // 替换构造器
        eval(Name + '= cls.Create');
        return cls;
      }

      return _Class;
    }();
    ----------

      1. 类数据块(CDB)与实例数据块(IDB)
      ----------
      这是在Qomo中两个最重要的数据结构。籍由JavaScript中严格的上下文环境带来的特性,
    Qomo为每个类封装了一个类数据块(CDB, Class Data Block)。同时,也为每一个构造的对
    象封装了一个实例数据块(IDB, Instance Data Block)。

    在上面的框架代码中,我们看到CDB与IDB都是通过new()关键字来创建的。不过,不同的是,
    InstanceDataBlock()是一个普通的构造函数,它返回由JavaScript的new()关键字创建的对
    象实例Data。而ClassDataBlock()并不这样,它内部暂存了new()创建的对象实例(this),
    而将一个函数对象cls返回给外部。

    而我们看到,在_Class()中调用ClassDataBlock(),并将返回的函数对象cls作为结果值返
    回给外部。也就是说,如果我们用下面的代码:
    ----------
    function MyObject() {
    }
    TMyObject = Class(TObject, 'MyObject');
    ----------

    那么我们最后得到的TMyObject就是由ClassDataBlock()返回的cls。——而并不是真实的
    CDB数据块,真正的CDB被藏在了_Class()调用上下文环境的内部。

    由于我们允许用下面两种代码来创建一个实例:
    ----------
    var obj1 = new MyObject();
    // 或
    var obj2 = TMyObject.Create();
    ----------

    而在框架代码的内部,我们看到下面的代码:
    ----------
        // 替换构造器
        eval(Name + '= cls.Create');
    ----------

    相对于MyObject()来说,它事实上执行的是:
    ----------
        eval('MyObject = cls.Create');
    ----------

    也就是说,前面提到的两种创建对象实例的代码,最终都将调用到cls.Create。也就是
    TMyObject.Create()。

    我们也看到,在TMyObject.Create中,Qomo为实例创建了一个IDB:
    ----------
        cls.Create = function () {
            // ...
     var Data = new InstanceDataBlock();
            // ...
    ----------

    而现在,通过这种封装结构,Qomo具有了一些特性:
      - 每一个类都有一个CDB。
      - 类的各个实例都有各自的IDB,但统一拥有类的CDB。
      - 在类的外部不能直接访问到CDB。同样,在实例的外部也不能直接访问到IDB。


      2. “类”如何在构造期访问CDB
      ----------
      对于OOP系统来说,“类”通常是声明性的。也就是说,类一旦被声明,则类基本上只
    是代表一种特定的数据结构的“类型”。在Qomo当然也遵循这一原则。但Qomo不可能在
    JavaScript中“创建”一种自己的语法。——事实上,Qomo是不想创建这样一种语法,
    并用一个parser去解析它,然后要求开发人员重新理解、并在此基础上编程。

    因此,Qomo采用了一些带有“声明性语义”的函数,或者符合JS规范的代码来完成这件
    事。例如:
    ----------
    // 详细参见“实现篇(五)——类声明周期与对象构造周期”
    function MyObject() {
      Attribute(this, 'Value', 0);// 快速特性声明
      var v = _get('Data');       // 取(当前类的)父类的特性值
      _set('Data', v);            // 置特性值, 但不覆盖父类

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

    这里最重要的几个扩展函数就是_cls, _get, _set和Attribute()。在这几个函数的实
    现代码中,我们都可以看到:
    ----------
    _get = function(n) { return _get.caller.caller.get(n) }
    _set = function(n,v) { return _set.caller.caller.set(n, v) }
    _cls = function() { return _cls.caller.caller }

    Attribute = function() {
      // ...
        var i, argn=arguments.length;
        var Constructor = Attribute.caller;
        var cls = Constructor.caller;
    }
    ----------

    也就是说,它们通过“函数被调用时的上下文”来查找到“类引用(cls)”和“真实的
    构造函数(Constructor)”。这是因为这个Constructor总是在下面这样的环境中被调
    用的:
    ----------
        var Constructor = eval(Name);
        // ...

        var cls = function (Constructor) {
          // ...
          base=new Constructor();
        }

        cls.OnClassInitializtion(Constructor);
        cls(Constructor);
        cls.OnClassInitialized(InstanceDataBlock);
    ----------

    而所谓Constructor,则是用户原始的构造器函数(而不是后来被替换的cls.Create)。
    因此,如下面的代码:
    ----------
    function MyObject() {
      Attribute(this, 'Value', 0);// 快速特性声明
      var v = _get('Data');       // 取(当前类的)父类的特性值
      _set('Data', v);            // 置特性值, 但不覆盖父类

      // ...
    }
    TMyObject = Class(TObject, 'MyObject');
    ----------

    在这个类的构造周期中,Attribute()调用中的caller就是Constructor(亦即是MyObject),
    而Constructor.caller就指向cls()这个函数,这就是Attribute()中下面代码的来源(其它
    几个函数类同):
    ----------
        var Constructor = Attribute.caller;
        var cls = Constructor.caller;
    ----------

    而cls()调用发生在一个类初始化(OnClassInitializtion)和结束化(OnClassInitialized)之
    间。Qomo在初始化中写着:
    ----------
        cls.OnClassInitializtion = function(Constructor) {
          if (Parent) Constructor.prototype = getPrototype(Parent);
          this.all = all;
          this.map = getInheritedMap;
          this.get = getAttribute;
          this.set = setAttribute;
          this.attrAdapter = getAttribute;
        }
    ----------

    这样,cls.get()、cls.set()等特性在“类构造周期”就可用了。而接下来:
    ----------
        cls.OnClassInitializtion = function(Constructor) {
          delete this.all;
          // more ...
        }
    ----------

    又把这些属性和方法给清除掉。使得“类构造周期”之外不可能再通过这些方法来访问CDB。


      3. “对象(实例)”如何访问IDB
      ----------
      对象实例(自身)的构造过程其实是由new ()关键字完成的。如下面的代码:
    ----------
    var obj2 = TMyObject.Create();
    ----------

    这时进入Create()方法时,this指向TMyObject自身。因此_Class()中下面的代码被调用:
    ----------
        cls.Create = function () {
          if (this===cls) {
            // 'this' is class ref.
            var i, v=arguments, n=v.length, s='new this.Create(';
            if (n>0) for (i=1,s+='v[0]'; i<n; i++) s += ', v[' + i +']';
            return eval(s+');');
          }
          // ...
    ----------

    这段代码实际上是重新调用new this.Create(),并传入参数。而我们前面提到过_Class()
    将会把构造器替换成cls.Create()。因此这段代码就达到了使下面两种语法等义的效果:
    ----------
    var obj1 = new MyObject();
    // 或
    var obj2 = TMyObject.Create(); // 实际将调用上一种语法
    ----------

    而同样的道理,new MyObject()其真实的调用会是“new cls.Create()”,这种情况下,在
    Create()函数中,this对象将指向新创建的实例,且this.constructor === cls.Create。
    所以将会调用到下面的代码:
    ----------
        cls.Create = function () {
          // ...
          else if (this && this.constructor===cls.Create) {
            // Make a DataBlock for per Instance, and reset attributes getter/setter.
            var Data = new InstanceDataBlock();
            this.get = Data.get;
            this.set = Data.set;
            this.inherited = Data.inherited;
          // ...
    ----------

    这样一来,新的对象实例(this)将会执有cls.Create()的一个上下文,并通过创建一个IDB
    的实例Data。然后,通过"this.get = Data.get"这样的代码,使对象实例执有一些访问IDB
    内部私有数据的方法。这样一来,我们在外部代码中就可以get/set/inherited,但却无法
    存取到IDB(也就是私有变量Data)中的数据了:
    ----------
    var obj = new MyObject();

    // 下面的代码将实际存取Data中的私有数据
    obj.set('Value', 100);
    ----------

      4. 对象构造周期:如何统一原型构造与类构造体系
      ----------
      有两种方法来实现“类构造体系”。其一是用类抄写,也就是试图用下面这样的代码:
    ----------
    var clrRef = TObject;
    var obj = New TMyObejct();

    for (var i in clrRef) obj[i] = clrRef[i];
    ----------

    我们的确可以通过这种方法来使得obj拥有一份TObject中的对象方法、属性与数据存取
    界面。——事实上这也是Qomo的前身WEUI采用的方法。——但是这样实现的效率是极低
    的。

    在Qomo中,采用了第二种方法,也就是通过“原型继承”来实现“类继承”。在这样的
    方案中,开发人员看到的会是“类继承”体系中的代码,而Class()关键字则在注册过程
    中隐含地完成了原型链的维护:
    ----------
      function setClassTypeinfo(cls, Attr, instance) {
        // ...
        cls.Create.prototype = instance;
      }

      function ClassDataBlock()
        cls.OnClassInitializtion = function(Constructor) {
          if (Parent) Constructor.prototype = getPrototype(Parent);
          // ...
        }

        var cls = function (Constructor) {
          // ...
          setClassTypeinfo(cls, Attr=new Attr(), base=new Constructor());
        }
       
        returtn cls;
      }
     
      function _Class() { 
        var cls = new ClassDataBlock(Parent, Name);
        cls.OnClassInitializtion(Constructor);

        cls.Create = function () {  /* ... */ }
        cls(Constructor);

        eval(Name + '= cls.Create');
        return cls;
      }
    ----------

    我们看到,“类注册Class()”调用的本质,是:
      - 创建CDB,并生成类的原型base. 这个原型是通过用户的构造函数Constructor来创建
        的,它的原型指向父类的原型:getPrototype(Parent)
      - 对象构造函数指向cls.Create(),而在cls()调用过程中将setClassTypeinfo()。这个
        过程传入的instance来自于类的原型,也就是"base=new Constructor()"。这意味着
        “类只是声明并实现了原型”,而对象“创建自类的一个(原型)复制”。

    使用这种技术达到的效果,与前面提到的“类抄写”是一致的。但由于利用了系统内置的
    原型机制、写复制机制和继承关系,因此效率上将高许多。而且由于“对象构造”其实基
    于一个“类实例的原型”,因此下面的代码就将“对象构造周期”与“类构造周期”分离
    开来了:
    ----------
      function _Class() {
        // ...
        cls.Create = function () {
            // ...

     // 如果有Create(), 则调用以启动“对象构造周期”
            if (this.Create) this.Create.apply(this, arguments);
        }
      }

    function MyObject() {
      // 下面的代码使类原型得到了Create()方法, 它被理解为对象构造周期的入口
      this.Create = function() {
        // 在下面的代码中的this即是对象实例
      }
    }
    TMyObject = Class(TObject, 'MyObject');
    var obj = new MyObject();
    ----------


    二、Qomolangma中的特性(Attribute)系统
    ~~~~~~~~~~~~~~~~~~

    在充分意识到原型继承的优点之后,Qomo在FT4之后的代码中,采用了类似实现“类继
    承”机制的代码来实现了特性(Attribute)系统。——而在FT4发布的Object.js代码中,
    采用的则是类似于“类抄写”的机制。

    Qomo为每一个类保存了一个类引用,这被放在一个名为"Class Type Info"的结构中,
    ----------
      function ClassTypeinfo(cls, Attr) {
        this.class_ = cls;
        this.$Attr_ = Attr;
        this.next__ = _classinfo_[cls.ClassName];
      }
    ----------

    全局对象"_classinfo_"用于保存所有类信息的入口,如果两个类名一致,则该入口被
    理解为一个链表。——这用于处理不同命名空间上的同名类。——而_classinfo_只是
    Class()上下文的全局可见,在Global全局则不可见。这避免了外部的代码修改它。

    在Qomo的类系统中,构建“类类型信息”时,采用的是如下的代码:
    ----------
      function setClassTypeinfo(cls, Attr, instance) {
        // ...
        // 查找类信息入口并处理重复注册问题

        _classinfo_[n] = new ClassTypeinfo(cls, Attr);
      }

      function ClassDataBlock() {
        var Attr = function() {}; // all getter and setter method of attributes

        var cls = function (Constructor) {
          var base, parent = getPrototype(cls.ClassParent);
          if (cls.ClassParent) Attr.prototype = getAttrPrototype(cls.ClassParent);
          setClassTypeinfo(cls, Attr=new Attr(), base=new Constructor());
          // ...
        }
    ----------

    正是这些代码展示了Qomo如果驾驭原型继承机制来实现复杂的Attribute系统。


      1. Attribute系统的构建
      ----------
      Attr首先是一个空的函数。准确地说,它是一个原型系统中的构造器。接下来,cls()函
    数中检测如果cls存在父类ClassParent,则通过getAttrPrototype()获得父类的Attriburte
    原型,并置为构造器Attr的原型“Attr.prototype”。

    接下来的使用Attr()来构造原型,并替换掉这个不现使用的构造器。因而Attr=new Attr()
    执行之后,Attr将是一个有效对象实例。它携带了所有从父类的Attribute中“原型继承”
    得来的读写器(getter/setter),以及特性值(AttributeValue)。

    接下来,“base = new Constructor()”这行代码开始调用。由于在Constructor中可以添
    加新的读写器,以及声明Attribute。例如:
    ----------
    // 用户的Constructor
    function MyObject() {
      this.getData = function() { /* ... */ }
      Attribute(this, 'Width', 100, 'rw');

      // ...
    }
    ----------
    这样一来,返回的base对象将与父类的原型存在差异。差异中如果有Attribute,那么应该
    当记入Attr对象。因此就有了下面的代码:
    ----------
        var cls = function (Constructor) {
          // ...
         
          for (var i in base) {
              // ...
       if (_r_attribute.exec(i)) {
                Attr[i] = base[i], delete base[i];
                if (!(RegExp.$2 in Attr)) Attr[RegExp.$2] = undefined;
              }
          }
        }
    ----------

    这段代码用于分析base中的属性i是否是getter/setter,如果是,则在Attr上保存一个引
    用,并从base(类原型)上删除这个属性。——而后,(如果需要)再在Attr上建立一个与特
    性同名的属性,用于存值。

    相对于上面的MyObject(),这段cls()代码的处理使得该类拥有如下的一个Attr:
    ----------
    // 等效于如下声明
    Attr = {
      Width: 100,
      Data: undefined,
      getData: function() { /* ... */ }
    }
    ----------

    而这个Attr被记入到typeinfo。对其子类来说,就可以通过getAttrPrototype()来获取并
    作为自己的原型,从而创建起Attribute的原型继承链。


      2. Attribute的读写
      ----------
      对于用户代码来说,读写Attribute的其实是对象实例。因此,这个读/写方法应该是对
    象的方法。而刚才我们看到Attr其实是在ClassDataBlock内部,也就是类的内部、对象的
    外部。这意味着我们不能在对象中直接存取到它。

    Qomo通过类的OnClassInitializtion/OnClassInitialized,来打通了“对象->类”访问的
    通道。具体的策略,就是在“对象可访问的上下文中,保存了三个对象方法的指针”:
    ----------
      function _Class(Parent, Name) {
        // ...
       
        // some member reference for class
        var $all = cls.all;
        var $map = cls.map;
        var $attr = cls.attrAdapter;  // 保存给Attribute系统使用
        function InstanceDataBlock() {
          // ...
        }
      }
    ----------

    这样,在IDB中就可以通过$attr来访问对父类的Attr中的getter/setter()。——尽管在
    后面的代码中将使得这是不必须的,但Qomo中还是保留了这一做法。

    接下来,当类完成初始化时:
    ----------
      function ClassDataBlock() {
        cls.OnClassInitialized = function(IDB) {
          // ...
          if (Parent) IDB.prototype = getAttrPrototype(cls);
        }
      }
     
      function _Class(Parent, Name) {
        // ...
        cls(Constructor);
        cls.OnClassInitialized(InstanceDataBlock);
      }
    ----------

    我们看到IDB的原型被置为了当前类(cls)的Attribute原型。这使得在对象构造期间,我
    们通过new InstanceDataBlock()得到的实例(this)将是与类的Attr原型等同的一个实例:
    ----------
        function InstanceDataBlock() {
          var data_ = this;

          this.get = function (n) {
            //...
            // 处理不同的调用参数

            return data_[n]; // a value
          }
        }
    ----------
    因此obj.get(n)就可以简单地返回data_[n]。当然也可以快速地处理到Attr中保存过的
    getXXXXX()函数。——如果它存在的话:
    ----------
          this.get = function (n) {
            //...
            // 处理不同的调用参数

            else {
              // get custom-built getter from cls's $Attr.getXXXXXX
              // in ClassDataBlock, the ref. equ data_['get'+n]
              var f = $attr('get'+n);
              if (f) return f.call(this, n);
            }
          }
    ----------


      3. “类构造周期”中的Attribute读写
      ----------
      “类构造周期”中对Attribute的读写与对象实例的读取稍有差异,但本质上是一致
    的。因为类中Attr对象既是子类的Attr的原型,也是一个私有的对象。因此:
    ----------
        function getAttribute(n) { return Attr[n] }
        function setAttribute(n, v) { Attr[n] = v }

        cls.OnClassInitializtion = function(Constructor) {
          // ...
          this.get = getAttribute;
          this.set = setAttribute;
        }
    ----------

    类引用(this)上的get/set,是留给“类构造周期”中的_set()/_get()和Attribute()
    来使用的。例如_set的代码就是"_set.caller.caller.set(n, v)"。


    三、Qomolangma中如何用inherited来调用父类方法
    ~~~~~~~~~~~~~~~~~~

    obj.inherited()试图让当前对象具有访问父类方法的能力。这种能力应该是内置于系统的,而
    不是象一些其它OOP框架那样,要求写如下的代码:
    ----------
    this.method = function() {
      this._parent.method.apply(this, arguments);

      // do other..
    }
    ----------

    尽管inherited()的含义,的确与上面的代码达到的效果一致。但如果让开发人员都这样写代码
    却实在有点勉强,况且还需要在对象系统中再维护_parent。——当然,在事实上开发人员也可
    以通过this.constructor.prototype来得到它,而这样处理也更加麻烦。

      1. inherited的实质是父类原型的遍历
      ----------
      Qomo试图把复杂的事物变得简单一些。尽管在这些简单的背后,是和原始情况一样、甚至有
    过之的复杂。Qomo的obj.inherited()的实现中最核心的一行代码是这样:
    ----------
            var p = cache[cache.length] = $map.call(this, f).slice(1);
    ----------

    其中$map来自于父类的CDB中getInheritedMap()函数的一个引用:
    ----------
      function ClassDataBlock() {
        // ...

        // 在CDB中,在类初始化阶段公布getInheritedMap
        cls.OnClassInitializtion = function(Constructor) {
          // ...
          this.map = getInheritedMap;
        }
      }

        // 获取getInheritedMap()的一个引用
        var $map = cls.map;

        function InstanceDataBlock() {
          // ...
          this.inherited = function(method){
            // 调用 $map()
          }
        }
    ----------

    而getInheritedMap用于从当前类及其原型中查找指定方法的call map。这个map就是一个栈,用
    于收集在当前类及父类中所有被覆盖(override)的方法。栈顶是调用inherited()方法时的这个
    函数。

    在类及父类中获取call map的代码并不复杂。我们遍历所有的父类prototype,然后通过调用检
    测方法/属性的函数hasOwnProperty(),来判定该方法是否被修改过(一旦该方法被覆盖,那么
    hasOwnProperty()的返回结果将是true):
    ----------
      var n=getPropertyName(method, this);
      // ...
     
      // 由于处于CDB, 所以能直接访问cls引用
      $cls = cls;
      while ($cls) {
        p = getPrototype($cls);
        if (p.hasOwnProperty(n)) a.push(p[n]);
        $cls = $cls.ClassParent;
      }
    ----------

    上面的代码中getPrototype()是在Class()所保留的_classinfo_中,通过类名来查找它的原型
    引用。

    然而我们也发现一个问题,即使通过name字符串在_classinfo_效率不错,但我们也需要通过
    不断的原型遍历来查找整个的call map。因此如果每次obj.inherited()都要重复这个过程的
    话,那么效率将会很差。

    因此,Qomo为每一个call map建立了一个缓存。它基于两个规则:
      - 即使是大型系统中,inherited()的使用也不是对每个方法的,也就是需要inherited()
        的方法数小于对象方法数。
      - 如果A方法试图inherited(),那么它每次调用inherited()总会得到相同的方法引用。

    可见每次都通过getPropertyName()来得到name,并通过name访问cache的方法并不可取。因
    为getPropertyName()本身就需要遍历一次对象方法和属性。因此Qomo建立的call map缓存的
    第一个结点并不是PropertyName,而是当前调用inherited()的方法本身:
    ----------
    this.inherited = function(method){
      var f=this.inherited.caller, args=f.arguments;
      // ...

      // find f() in cache, and get inherited method p()
      for (var p, i=0; i<cache.length; i++) {
        // 查找cache,并检测map[0]是不是指定的方法f()
        if (f === cache[i][0]) {
          p = cache[i];
          p.shift();                      // 移除当前方法
          return p[0].apply(this, args);  // 调用父类方法
        }
      }
    }
    ----------


      2. 同一对象方法中,多次inherited()调用的实质是call map栈的出栈
      ----------
      上面提到了call map是一个栈,它的建立是通过getInheritedMap()对父类原型进行遍历。
    而inherited()通过cache[i][0]查找到当前正在调用的函数和call map。

    这样一来,在A去inherited(B)时,B如果又inherited(D),那么D调用obj.inherited(),仍
    然会回到这个函数,并见到cache[i][0]上的方法D。这时,call map的下一个方法就可以被
    取出来,并执"p[0].apply(this, args)"操作。

    很明显,如果有多次的inherited(),那么整个的行为看起来就象是cache[i]中的元素在出栈。

    问题是:如果到达最后一个元素呢?

    根据inherited()的语义,我们的确有可能在一个类的方法中试图去inherited()一个父类中
    不存在的方法。——你或者写错了类,或者是开发人员用错了inherited(),总之这是必将会
    发生的一件事。

    Qomo为此做好了准备。Qomo在构建call map的时候,为栈底(数组最末元素)填入了一个名为
    $inherited_invalid的方法。这个方法将触发一个异常。

    而如果一个方法在父类中根本没有同名的(被覆盖的)方法,那么在cache中它将有一个如下格
    式的call map:
    ----------
    a = [method, $inherited_invalid];

    // 该call map被填入_maps末尾
    _maps[len] = a;
    ----------

    很显然,如果要对这个method进行inherited(),那么将立即执行到$inherited_invalid()并
    导致一个异常的触发。


      3. 在Attribute的读写器中的inherited()
      ----------
      相对于Attributes系统,Inherited实现起来更为复杂。其中的因素之一,就是Inherited事
    实上也需要实现一部分Attributes的功能。例如:
    ----------
    function MyObject() {
      this.getData = function() {
        return 100
      }
    }

    function MyObjectEx() {
      this.getData = function() {
        return this.inherited() * 2
      }
    }

    TMyObject = Class(TObject, 'MyObject');
    TMyObjectEx = Class(TMyObject, 'MyObjectEx');

    var obj = new MyObjectEx();
    alert(obj.get('Data'));
    ----------

    在这个例子中,我们见到读写器方法getData()也需要调用父类方法。而我们也知道,“父
    类方法MyObject.getData()是被Attributes系统隐藏的,因此如果要Inhterited()访问到,
    就需要Attribute提供相应的机制。

    但这在Qomo中并不麻烦。因为建立call map的getInheritedMap()运行在CDB的上下文中,
    因此它可以访问内部的Attr对象的属性和原型。所以getInheritedMap()中的实现代码并不
    复杂:
    ----------
    function inheritedAttribute(foo) {
      // ...
      var p, v=[], $cls=Parent;
      while ($cls) {
        p = getAttrPrototype($cls);
        if (p.hasOwnProperty(n)) v.push(p[n]);
        $cls = $cls.ClassParent;
      }
      if (v[0] !== foo) v.unshift(foo);
      return v;
    }

    function getInheritedMap(method) {
      //...
      var a=inheritedAttribute(method);
      // ...
     
      a.push($inherited_invalid);
      return (_maps[len] = a);
    }
    ----------


    四、Qomolangma中的一些问题
    ~~~~~~~~~~~~~~~~~~

    事实上我们已经发现,Qomo为IE 5.0兼容做出了太多的牺牲。例如在common_ie5.js中
    使用了大量的代码来提供与IE5.5+相等同的RegExp.replace(),也使用了较低效的方式
    来避让了IE 5中存在的RegExp.lastIndex的BUG。

    即使如此,我们仍旧认为这些兼容的工作是值得的。然而现在,在Object.js之后,我
    们终于发现了无可回避的问题。这主要体现在两个方面:
      - <property> in <object> 的使用
      - apply与call的实现

      1. <property> in <object> 的使用
      ----------
      在Object.js之前的代码中,都已经尽可能地避开了不能在IE 5.0中使用的<property>
    in <object>语法。然而我们也看到,使用下面的代码并不能真正的解决问题:
    ----------
    if (typeof object[property] == 'undefined') {
      // ...
    }
    ----------

    这样的语句事实上并不能替代<property> in <object>。因为如果"obj[n]=undefined",
    那么就将再也无法通过这段代码来完成检测。因此更有效的代码是:
    ----------
    function hasProperty(p, o) {
      for (var i in o) if (p==i) return true;
      return false;
    }
    ----------

    然而我们不能要求开发人员为了兼容IE 5.0都使用hasProperty()来替代"<p> in <o>"的
    语法。因此需要在IE 5.0中引入一个parser,对$import()的.js文件中的代码进行替换。
    然而这件工作变得异常坚巨。其中最重要的原因是:
      - 我们无法快速地识别for (p in o)与if (p in o)之间的差异
      - 我们也无法对类似于if (!(o in o))这样多层的运算进行快速、有效的parser

    因此"<p> in <o>"已经让我们在IE 5兼容的道路上遇到了大麻烦。但即使如此,这一切
    仍不是最重要的。因为Zhe(fangzhe@msn.com)已经开始尝试一个更强大的parser,用于
    解决Mozilla与safari上的一些BUG修补。如果他的工作能有一些进展,则"<p> in <o>"
    的问题仍然有望解决。

    真正的问题出在call()与apply()。

      2. apply与call的实现
      ----------
    在IE 5上实现apply与call并不难。其基本的做法是:
    ----------
    Function.prototype.apply = function(obj, args) {
      for (var i=0, arr=[]; i<args.length; i++) arr.push('args[' + i + ']');
      obj._func = this;

      return eval('obj._func(' +
        arr.join(',') +
        ')'
      )
    }

    // call()调用apply()并传入参数表
    // Function.prototype.call = ...
    ----------

    这样的代码将得到很好的执行。事实上细节可以掩饰得更好,例如Qomo中就在eval()
    执行的字符串中加入了一行"delete obj._func"来处理掉这个多余的属性。

    在apply()的实现中,采用eval()的真正原因是:只有eval()才能访问上下文中的变量
    obj和args,换做execScript()或者使用new Function()来创建函数并执行都不能取得
    这种效果。

    然而eval()执行出现了另一个问题,也就是在eval()中调用obj._func()时,_func()
    上下文件中得到的caller为null:
    ----------
    function foo(v) {
      alert(foo.caller);
      alert(v);
    }

    function foo1() {
      var a = 100;
      eval('foo(a)');
    }

    foo1();
    ----------

    这对于一般的代码来说并没有什么影响。然而Qomo在实现inherited()的时候,以及
    实现obj.get/set方法时,使用了caller属性来取得调用者函数的引用。然后get/set
    和inherited过程中,将大量使用caller来处理多层调用间的关系。

    这些技巧是使得Qomo有能力处理this.get()这种简略语法的关键。然而多层的调用却
    不得不通过apply()来传递参数。——显然“IE5上apply()中的代码得不到caller”
    已经成为致命的问题了。

    鉴于此,Qomo从FT4开始将暂停对IE 5.0的兼容性支持。

  • 相关阅读:
    寒假学习进度8
    寒假学习进度7
    寒假学习进度6
    寒假学习进度5
    寒假学习进度4
    寒假学习进度3
    寒假自学进度13
    Python引用某一文件的方法出现红色波浪线
    寒假自学进度11
    正则表达式(学习)
  • 原文地址:https://www.cnblogs.com/encounter/p/2188706.html
Copyright © 2011-2022 走看看