Open-Source World

let's learn and study.
posts - 28, comments - 23, trackbacks - 0, articles - 1
说起OO,我们首先想到的肯定是抽象,继承,多态,重载等一系列的名词。而在JavaScript语言中,因为它是基于对象的(Object-Based),并不是面向对象的(object-oriented),所有它并没有提供这些能力。它仅仅具有一些面向对象的基本的特征而已(比如您可以使用 new 语句来调用一个构造函数)。
     但在prototype中通过一些巧妙的方式实现了对象的抽象与继承, 通过使用Object.extend方法,它甚至可以允许我们实现多重继承.
     下面我们来看Enumerable的each()方法实现:
    
     第一步:
       each: function(iterator) {
       var index = 0;
       try {
         this._each(function(value) {
           try {
             iterator(value, index++);
           } catch (e) {
             if (e != $continue) throw e;
           }
         });
       } catch (e) {
         if (e != $break) throw e;
       }
    }
     第二步
     Object.extend(Element.ClassNames.prototype, Enumerable);
    
     我们看到第一步中each方法内部调用到this._each方法,但this._each方法在Enumerable并未做定义,而是将其定义放在Element.ClassNames.prototype中。第二步就是使用Object.extend扩展Element.ClassNames.prototype的内容。整个过程从Java的角度,相当于定义一个抽象类Enumerable,它提供了对枚举对象的各种迭代操作,但迭代内容的方式却通过抽象的方法_each()下放给子类做具体实现, 充分实现了抽象与继承的功能。整个效果还有另一种实现方式是利用Adapter Pattern来实现转调用,但这已经不是抽象与继承的范畴了。
    
    上面例子中我们注意到最重要的一步是Object.extend这个方法的调用了, 看这个方法的源代码我们发现,这仅仅是子类型对父类型的所有property的拷贝,看似简单,但它却帮我们丰富了代码重用的方式,也利于我们用OO的思维观察一个类型了。另外,关于多态在JS中谈是没有意义的,重载JS默认就支持了,只是在调用父类同名方法里需要通过call方法来完成,但在prototype中并未做这种重载的实现, 这里就不再多做讨论了,有兴趣的朋友可以交流。
   
    此外,prototype中还提供了Class这个类型,它只包含一个create方法用于创建一个function对象,并提供默认的初始化构造函数initialize。乍看之下,这个方法功能很是简单,用处不大,我们完全可以通过JS提供的new function()的方式来实现。但其实作者的巧妙就是在于此,它将一个类型(class)的定义跟一个方法(function)的定义以一种显示编码的方式来作了区分,很有利于代码的后期维护,建议使用prototype框架的朋友尽量使用Class.create创建你的自定类型。
   
    最后,我们来关注一下Function.prototype.bind与Function.prototype.bindAsEventListener这两个方法, 在prototype中许多自定义对象的封装, 或者我们自定义的对象封装中都大量使用到了这两个方法, bind方法主要的功能就是应用某一对象的一个方法,并用另一个对象替换当前对象。bindAsEventListener类似,但它只提供event做为入口参数。它们主要使用了javascript的apply方法与call方法,不明白可以先看我写的API Document中的Function类中对这两个方法的定义说明与示例。
    
    接着先来看prototype源代码中对 Function.prototype.bind 的定义
 
        Function.prototype.bind = function() {
       var __method = this, args = $A(arguments), object = args.shift();
       return function() {
         return __method.apply(object, args.concat($A(arguments)));
       }
    }
    
 bind方法return的是一个function(),在function()函数内部又用_method.apply的方法做一个转调用。这种封装其实是把apply的绑定时即运行封装成调用时才绑定后运行的方式,这样的一个封装非常有利于我们实现对象的组件化实现。有写过的Microsoft的Behavior的朋友知道,Microsoft的支持这个技术,其实就是一种把脚本程序完全从HTML页面中分离出来的方法,它对页面元素直接定义行为脚本,在行为脚本中可以以this的引用直接取得元素。而在低版本的浏览器不支持apply方法与call方法时候,我们很难以做到这点,常常需要以附加属性,全局变量等一些方式做中间数据保存与交换,耦合度高,聚合度低,举个简单的例子来观察一下:

   例子: 我们想让一个的BOTTON元素赋于一些行为" 累计自身被点击的次数"
   第一种方法是最简单的,我们直接给这个botton元素添加onclick方法来累加(如下:代码1)。但这种方式只能应用在一些简单的前台脚本上面,如果我们需要写一个具有大量行为的元素时,比如写一个Rich Table,我们就希望它最好应该是个组件了。这样,我我们只要通过一些简单的定义就可以使它自动具有一系列的行为。
    代码1: <input  type="button" value="100" onclick="this.value = parseInt(this.value) + 1"/>
    
     第二种方法(代码2: ) 我们通过一个普通function定义一个类型,初始化这个类型,传入要产生行为作用的按钮ID,被指定的按钮就自动具有" 累计自身被点击的次数"的功能了,有效的实现了HTML元素与脚本代码的执。
      
     代码2:
      <script language="javascript" src="prototype.js"></script>
      <SCRIPT language="javascript">
      window.onload = function(){
       new NonBindClass("btn1");
      } 
      
      var NonBindClass_Current_Element = null;
      function NonBindClass(element){
       this.element = $(element);
       if(!(this.element || false)){ return null;}       
       this.element.attachEvent('onclick',printValue);
       NonBindClass_Current_Element = this.element;
       function printValue(){
        //注意,这里的this并不是发生当前动作的主体或者当前NonBindClass的实例的引用! 所以我通过中间变量或全局变量的方式来取得元素
        NonBindClass_Current_Element.value = parseInt(NonBindClass_Current_Element.value) + 1;
       }
      }
      </SCRIPT>
      <input id="btn1" type="button" value="100"/>      
    但是这里我们看到printValue方法中使用NonBindClass_Current_Element这个全局变量来取得定义元素的对象, 但这种全局变量常常让人无比的头痛! 而且, 这时候如果需要给另一个按钮btn3也添加同样行为,那么全局变量NonBindClass_Current_Element会被重新指向到btn3,而对于btn1的点击事件也会随之作用到btn3,使得我们无法达到目的。当然,我们可以通过其它的方式来达到目的,比如让printValue方法内部可以直接引用到指定的对象:  一种办法就是传入参数,但这种方法在做attachEvent方法绑定时会直接做调用到printValue方法! 还有一种办法 就是使用prototype的bind方法,我们可以给printValue绑定任意的对象,bind(this)或者bind(this.element)都可以达到目的,具体请看 代码3:
    代码3:
     <script language="javascript" src="prototype.js"></script>
     <SCRIPT language="javascript">
     window.onload = function(){
      new BindClass("btn1");
      new BindClass("btn2"); 
     } 
     
     function BindClass(element){
      this.element = $(element);
      if(!(this.element || false)){ return null;}       
      this.element.attachEvent('onclick',printValue.bind(this.element));
      function printValue(obj){
       this.value = parseInt(this.value) + 1;
      }
     }
     </SCRIPT>
     <input id="btn1" type="button" value="100"/>  
     <input id="btn2" type="button" value="200"/>  
     代码更加精简,内聚度也高,并且支持给多个不同的元素赋予同样行为。
     Function.prototype.bindAsEventListener的功能类似,差别在于它只传入固定的一个event参数,用于事件后期运行时绑定调用.

只有注册用户登录后才能发表评论。


网站导航:
博客园   IT新闻   Chat2DB   C++博客   博问