簡單的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()// => trueapp

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

這個實現中有幾點須要注意:
一、建立一個構造類必需要簡單(這種狀況下簡單的提供一個init方法就能起做用);
二、要建立一個新的‘class’就必須擴展(sub-class )已經存在的類;
三、全部的 ‘classes’都從一個祖先繼承而來:Class。所以若是你想建立一個新類分支,這個新類分支就一定是 Class的子類;
四、最有挑戰一點的是:獲取被 覆寫 了的但 必須被提供 的方法(這些方法的上下文被正確設置)。上面用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;
  };
})();this

在我看來,最棘手的兩個問題是 "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 );
  }
});spa

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

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

實現這一功能是一個多步的過程。開始的時候,注意,咱們用來擴展一個已經存在的類的對象字面量 (好比被傳入到Person.extend裏的那一個)須要merge到基礎的new Person實例(該實例的結構在前面已經被描述過)。在這個  merge 的過程當中,咱們作了一個簡單的檢查:咱們正在合併(merge)的屬性是否是不一個函數?正在被咱們替換的是否是也是一個函數?若是條件成立的話咱們須要作一些事來建立一種方式使得咱們的父類方法依然能工做。
注意,咱們建立了一個匿名閉包(該閉包返回一個函數)來封裝這個新的父類加強了的方法。開始的時候咱們須要作一個合格市民,把引用保存到老的this._super(若是它確實存在的話忽略它),在咱們作完相應工做後再恢復它。這對於有相同名字的變量已經存在的狀況很是有用(不要期望能意外的換走它)。
接下來咱們建立新的_super方法,它僅僅是已經存在於父類prototype中的方法的一個引用。幸運的是咱們不須要作額外的變動,或者從新界定範圍,當函數是咱們對象的屬性的時候它的上下文環境將被自動設置(this將會指向咱們的實例而不是父類)。
最後咱們調用咱們原始的方法,在咱們恢復_super到它原始的狀態而且從函數返回值以後它會完成它的工做(也可能要用到_super)。
針對上面的狀況,已經有若干種有相似結果的方式能夠實現(我已經看到了一種經過arguments.callee來綁定父類的方法到該方法自己的方式來實現),可是我感受個人這種技術提供了實用性和簡潔性的最佳組合。
在我要完成的工做中我要覆蓋更多的隱藏在JavaScript prototype背後的本質和細節,可是就這個Class類的實現,我想讓更多的人來嘗試它並運用它。我認爲對於簡潔的代碼(更容易學習、擴展和下載)還有不少要說,因此我認爲要了解JavaScript類構造和繼承的基礎,這個實現是一個很好的開始。(完)
相關文章
相關標籤/搜索