簡單的JavaScript繼承(譯)

這篇文章翻譯自John Resig(jQuery的做者)的博客,原文地址javascript

爲了正在寫的這本書(譯者注:這本書是《忍者祕籍》),我最近作了許多關於JavaScript繼承的工做,並在此基礎上研究了幾種不一樣的JavaScript經典繼承模擬技術。在我全部看過的研究中,我最推崇的是base2Prototype這兩個庫的實現。java

我想要提取這些技術的精華,以一個簡單的、可複用的方式進行展現,以便使這些特性更容易不依賴其餘的內容而被理解。此外我想要使其能夠被簡單的、高效的被使用。這裏展現了一個可使用完成後的結果來實現的實例。(譯者注:既完成後的代碼能夠用於實現下面這個功能)bash

var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  },
  dance: function(){
    return this.dancing;
  }
});

var Ninja = Person.extend({y
  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
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class複製代碼

關於本例,有幾點重要的注意事項。服務器

  • 讓構造器的建立更加簡單(在這個例子中僅僅使用init方法來建立)閉包

  • 爲了建立一個新的‘class’,你必需要繼承一個已經存在的類(sub-class).app

  • 全部的「類」都繼承於一個祖先:Class。所以,若是要建立一個新類,它必須是Class的子類。函數

  • 該語法最大的挑戰是訪問被覆蓋的方法,並且有時這些方法的上下文也有可能被修改了。經過this._super()調用Person超類的原始init()dance()方法學習

本例的代碼使我很愉快:它使得「類」的概念做爲一種結構,保持繼承簡單,而且容許調用超類方法。ui

簡單的類建立與繼承this

這裏是該內容的實現(合理的大小而且有備註) 大概有25行。 歡迎並感謝提出建議。

/* Simple JavaScript Inheritance
 * By John Resig https://johnresig.com/
 * MIT Licensed.
 */
//從base2與Prototype這2個庫中受到啓發。
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

 //基礎的class實現 沒有作任何事情
  this.Class = function(){};

  //  建立一個新的類繼承這個Class
  Class.extend = function(prop) {
    var _super = this.prototype;

    //   實例化一個基礎類(僅僅是建立實例,並無運行初始化構造器)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // 複製屬性到新的原型上
    for (var name in prop) {
      // 檢查咱們是否覆蓋了一個已經存在的方法
      prototype[name] = typeof prop[name] == "function" && 
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;

            // 添加._super()方法,該方法與超類的方法相同
            this._super = _super[name];

            //  該方法只須要臨時存在,因此在執行完以後移除該方法
            var ret = fn.apply(this, arguments);        
            this._super = tmp;

            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }

    //  仿真的類構造器
    function Class() {
      // All construction is actually done in the init method  全部的建立工做都會在init方法裏完成
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

    // 設置類的原型
    Class.prototype = prototype;

    //重載構造器的引用
    Class.prototype.constructor = Class;

    //讓類能夠繼續擴展
    Class.extend = arguments.callee;

    return Class;
  };
})();複製代碼

在我看來,最難的兩個部分是「初始化/不調用init方法」和「建立_super方法」。我想要簡要的介紹這部分以便於理解整個代碼的實現。

子類的實例化

爲了用函數原型模擬繼承,咱們使用傳統的建立父類的實例,並將其賦值給子類的原型。若是不使用以前的實現,其實現代碼相似以下:

function Person(){}
function Ninja(){}
Ninja.prototype = new Person();
// Allows for instanceof to work:
(new Ninja()) instanceof Person複製代碼

該代碼的挑戰在於咱們想從instanceof中受益,而不是實例化Person對象並運行其構造器。爲了抵消這一點,咱們在代碼中定義了initialozing變量,當咱們想使用原型實例化一個類的時候,都將該變量設置爲true。

所以,在構造實例的時候,咱們能夠確保不在實例化模式下進行構建實例,而且能夠相應的運行或者跳過init()方法。

if ( !initializing )
  this.init.apply(this, arguments);複製代碼

尤爲重要的是,init()方法能夠運行啓動各類昂貴的啓動代碼(連接到一個服務器,建立DOM元素等等),因此若是隻是建立一個實例做爲原型的話,咱們要避免任何沒必要要的昂貴代碼。

保留父級方法

當你正在實例化的時候,建立一個類而且繼承超類的方法,咱們保留了訪問被覆蓋方法的能力,最後在這個特別的實現中,使用了一個新的臨時方法(._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的這個)須要與基礎的new Person的實例合併(Person類以前已經被建立了)。在合併過程當中咱們作了簡單的檢查:子類屬性是不是一個函數、超類屬性是不是一個函數、子類函數是否包含了super引用。

注意,咱們建立了一個匿名的閉包(返回了一個構造函數),將會封裝並執行子類的函數。首先,做爲優秀的開發人員,須要保持舊的this._super引用(無論它是否存在),處理完了之後再恢復該引用。這在同名變量已經存在的狀況下會頗有用(咱們不想意外的失去它)。

接下來,咱們建立了新的_super方法,新的方法保持了對存在於父類方法的引用。值得慶幸的是,咱們不須要作任何額外的代碼修改或者做用域的修改,當函數成爲咱們對象的一個屬性時,該函數的上下文會自動設置(this引用的是當前的子類實例,而不是父類實例)。

最後咱們調用原始的子類方法執行本身的工做(也有可能使用了_super),而後將_super恢復成原來的狀態,並返回調用結果。

有不少方式能夠達到相似的結果(有的實現,會經過訪問arguments.callee,將_super方法綁定到方法自身),可是該特定技術提供了良好的可用性和簡便性。

我會在我寫的書中覆蓋更多的JavaScript原型系統背後的真相,我只是想把這個類實現放到這裏,讓每一個人都嘗試使用它。我認爲這個簡單的代碼能夠說明不少的事情(更容易去學習,去繼承,更少的下載),所以我認爲這個實現是開始和學習JavaScript類構造和繼承的基礎的好地方。


其實我是拜讀過忍者祕籍的,這個例子在忍者祕籍中的第六章 - 原型與面向對象中作了更加詳細的講解,此外本書全面且詳細的講解了javascript的基礎部分(函數、執行上下文、閉包等等),而且書中有部分例子實際上有些深奧,部分例子在我當初第一次閱讀的時候並無徹底理解,因而我就把該頁摺疊起來,往後再次閱讀理解更爲透徹一點。 因爲我是認真閱讀過忍者祕籍的,我認爲這本書很是的不錯(畢竟是jQuery做者寫的),所以在這裏我向各位初學者推薦這本書,但願對你們有所幫助。

相關文章
相關標籤/搜索