簡單的JavaScript繼承

openkk 12年前發布 | 18K 次閱讀 JavaScript開發 JavaScript
我想萃取有關繼承技術的精華到一個簡單、可重用、容易理解且不會有任何依賴的形式實現。此外,我也想讓這個結果簡單而且非常有用。這有一個我想要的效果的例子:
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類構造和繼承的基礎,這個實現是一個很好的開始。(完)
 本文由用戶 openkk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!