简单的JavaScript继承

openkk 12年前
我想萃取有关继承技术的精华到一个简单、可重用、容易理解且不会有任何依赖的形式实现。此外,我也想让这个结果简单而且非常有用。这有一个我想要的效果的例子:
var  Person =  Class . extend ( {
  init:  function ( isDancing ) {
     this . dancing  = isDancing;
   } ,
  dance:  function ( ) {
     return   this . dancing ;
   }
} ) ;

 

var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    // Call the inherited version of dance()
    return this._super();
  },
  swingSword: function(){
    return true;
  }
});

var p = new Person(true);
p.dance()// => true

var n = new Ninja();
n.dance()// => false
n.swingSword()// => true

// Should all be true
instanceof Person && p instanceof Class &&
instanceof Ninja && n instanceof Person && n instanceof Class

这个实现中有几点需要注意:
1、创建一个构造类必须要简单(这种情况下简单的提供一个init方法就能起作用);
2、要创建一个新的‘class’就必须扩展(sub-class )已经存在的类;
3、所有的 ‘classes’都从一个祖先继承而来:Class。因此如果你想创建一个新类分支,这个新类分支就必定是 Class的子类;
4、最有挑战一点的是:获取被 覆写 了的但 必须被提供 的方法(这些方法的上下文被正确设置)。上面用this._super()方法调用了Person父类原来的init()和dance()方法说明了这一点。
我对这个结果还是很满意的:它有助于增强‘classes’作为一个构造(structure)的概念,保持了简单的继承,并允许对父类的方法调用。
简单的类构造和继承
这有一个上面代码的实现(规模适度而且评论颇佳)-上下代码25行左右。反馈很好且被广泛接受。

// Inspired by base2 and Prototype
( function ( ) {
   var  initializing =  false , fnTest =  /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/ ;

 

  // The base Class implementation (does nothing)
  this.Class = function(){};
  
  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;
    
    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;
    
    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" && 
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;
            
            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];
            
            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);        
            this._super = tmp;
            
            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }
    
    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
    
    // Populate our constructed prototype object
    Class.prototype = prototype;
    
    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;
    
    return Class;
  };
})();

在我看来,最棘手的两个问题是 "initializing/Don't call init"和"create super method"。我想简单的涉及一下这些,以使你对这个方法中完成了什么有一个更好的理解。
初始化
为了用一个函数的prototype来模仿继承,我们用传统的技术来创建一个父类函数的实例化并把它分配给子类的prototype。不考虑上面的内容的话其实现大抵像这样:
function  Person ( ) { }
function  Ninja ( ) { }
Ninja. prototype  =  new  Person ( ) ;
// Allows for instanceof to work:
( new  Ninja ( ) )   instanceof  Person  
这里面的挑战是,我们需要利用instanceof的好处,而不是仅仅考虑实例化Person父类以及运行他的构造函数 的消耗。 为了中和这两者的效应,在我们的代码中有一个变量 initializing,无论什么时候我们想实例化一个类(唯一的目的是)来作为prototype的值,该变量都被设置为true。

因此当谈到实际的构造函数的时候,我们要确信我们不是在一个初始化模式,而是有条件的执行init方法:
if   (  !initializing  )
   this . init . apply ( this , arguments ) ;
尤其重要的是,init方法可以运行各种消耗很大的启动代码(连接到服务器,创建DOM元素,谁知道呢)所以绕过这个最终的工作会很有好处。
super 方法
当你在做继承的时候,你创建一个类从父类中继承功能,一个常见的要求是你要能获取已经被你重写的方法。结果,在这个特别的实现中是一个新的临时方法._super。这个方法是唯一可以通过子类的方法来引用父类的相同方法的途径。
比如,如果你想通过这项技术来调用父类的构造函数,你可以这样做:
var  Person =  Class . extend ( {
  init:  function ( isDancing ) {
     this . dancing  = isDancing;
   }
} ) ;

 

var Ninja = Person.extend({
  init: function(){
    this._super( false );
  }
});

var p = new Person(true);
p.dancing// => true

var n = new Ninja();
n.dancing// => false 

实现这一功能是一个多步的过程。开始的时候,注意,我们用来扩展一个已经存在的类的对象字面量 (比如被传入到Person.extend里的那一个)需要merge到基础的new Person实例(该实例的结构在前面已经被描述过)。在这个  merge 的过程中,我们做了一个简单的检查:我们正在合并(merge)的属性是不是不一个函数?正在被我们替换的是不是也是一个函数?如果条件成立的话我们需要做一些事来创建一种方式使得我们的父类方法依然能工作。
注意,我们创建了一个匿名闭包(该闭包返回一个函数)来封装这个新的父类增强了的方法。开始的时候我们需要做一个合格市民,把引用保存到老的 this._super(如果它确实存在的话忽略它),在我们做完相应工作后再恢复它。这对于有相同名字的变量已经存在的情况非常有用(不要指望能意外的换走它)。
接下来我们创建新的_super方法,它仅仅是已经存在于父类prototype中的方法的一个引用。幸运的是我们不需要做额外的变更,或者重新界定范围,当函数是我们对象的属性的时候它的上下文环境将被自动设置(this将会指向我们的实例而不是父类)。
最后我们调用我们原始的方法,在我们恢复_super到它原始的状态并且从函数返回值之后它会完成它的工作(也可能要用到_super)。
针对上面的情况,已经有若干种有类似结果的方式可以实现(我已经看到了一种通过arguments.callee来绑定父类的方法到该方法本身的方式来实现),但是我感觉我的这种技术提供了实用性和简洁性的最佳组合。
在我要完成的工作中我要覆盖更多的隐藏在JavaScript prototype背后的本质和细节,但是就这个Class类的实现,我想让更多的人来尝试它并运用它。我认为对于简洁的代码(更容易学习、扩展和下载)还有很多要说,所以我认为要了解JavaScript类构造和继承的基础,这个实现是一个很好的开始。(完)