簡單的JavaScript繼承

這篇文章翻譯自John Resig(jQuery的作者)的博客,原文地址能庆。

為了正在寫的這本書(譯者注:這本書是《忍者秘籍》)砖瞧,我最近做了許多關于JavaScript繼承的工作偎血,并在此基礎上研究了幾種不同的JavaScript經典繼承模擬技術霸株。在我所有看過的研究中雕沉,我最推崇的是base2Prototype這兩個庫的實現(xiàn)集乔。

我想要提取這些技術的精華去件,以一個簡單的、可復用的方式進行展示扰路,以便使這些特性更容易不依賴其他的內容而被理解尤溜。此外我想要使其可以被簡單的、高效的被使用汗唱。這里展示了一個可以使用完成后的結果來實現(xiàn)的實例宫莱。(譯者注:既完成后的代碼可以用于實現(xiàn)下面這個功能)

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

關于本例,有幾點重要的注意事項哩罪。

  • 讓構造器的創(chuàng)建更加簡單(在這個例子中僅僅使用init方法來創(chuàng)建)

  • 為了創(chuàng)建一個新的‘class’授霸,你必須要繼承一個已經存在的類(sub-class).

  • 所有的“類”都繼承于一個祖先:Class巡验。因此,如果要創(chuàng)建一個新類碘耳,它必須是Class的子類显设。

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

本例的代碼使我很愉快:它使得“類”的概念作為一種結構捕捂,保持繼承簡單,并且允許調用超類方法斗搞。

簡單的類創(chuàng)建與繼承

這里是該內容的實現(xiàn)(合理的大小并且有備注) 大概有25行指攒。 歡迎并感謝提出建議。

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

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

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

    //   實例化一個基礎類(僅僅是創(chuàng)建實例允悦,并沒有運行初始化構造器)
    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];

            //  該方法只需要臨時存在虑啤,所以在執(zhí)行完之后移除該方法
            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  所有的創(chuàng)建工作都會在init方法里完成
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

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

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

    //讓類可以繼續(xù)擴展
    Class.extend = arguments.callee;

    return Class;
  };
})();

在我看來澡屡,最難的兩個部分是“初始化/不調用init方法”和“創(chuàng)建_super方法”。我想要簡要的介紹這部分以便于理解整個代碼的實現(xiàn)咐旧。

子類的實例化

為了用函數(shù)原型模擬繼承驶鹉,我們使用傳統(tǒng)的創(chuàng)建父類的實例,并將其賦值給子類的原型铣墨。如果不使用之前的實現(xiàn)室埋,其實現(xiàn)代碼類似如下:

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

該代碼的挑戰(zhàn)在于我們想從instanceof中受益,而不是實例化Person對象并運行其構造器伊约。為了抵消這一點姚淆,我們在代碼中定義了initialozing變量,當我們想使用原型實例化一個類的時候屡律,都將該變量設置為true腌逢。

因此,在構造實例的時候超埋,我們可以確保不在實例化模式下進行構建實例搏讶,并且可以相應的運行或者跳過init()方法。

if ( !initializing )
  this.init.apply(this, arguments);

尤其重要的是霍殴,init()方法可以運行啟動各種昂貴的啟動代碼(鏈接到一個服務器媒惕,創(chuàng)建DOM元素等等),所以如果只是創(chuàng)建一個實例作為原型的話来庭,我們要避免任何不必要的昂貴代碼妒蔚。

保留父級方法

當你正在實例化的時候,創(chuàng)建一個類并且繼承超類的方法,我們保留了訪問被覆蓋方法的能力肴盏,最后在這個特別的實現(xiàn)中科盛,使用了一個新的臨時方法(._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類之前已經被創(chuàng)建了)蜡饵。在合并過程中我們做了簡單的檢查:子類屬性是否是一個函數(shù)、超類屬性是否是一個函數(shù)胳施、子類函數(shù)是否包含了super引用溯祸。

注意,我們創(chuàng)建了一個匿名的閉包(返回了一個構造函數(shù))舞肆,將會封裝并執(zhí)行子類的函數(shù)焦辅。首先,作為優(yōu)秀的開發(fā)人員椿胯,需要保持舊的this._super引用(不管它是否存在)筷登,處理完了以后再恢復該引用。這在同名變量已經存在的情況下會很有用(我們不想意外的失去它)哩盲。

接下來前方,我們創(chuàng)建了新的_super方法,新的方法保持了對存在于父類方法的引用廉油。值得慶幸的是惠险,我們不需要做任何額外的代碼修改或者作用域的修改,當函數(shù)成為我們對象的一個屬性時抒线,該函數(shù)的上下文會自動設置(this引用的是當前的子類實例班巩,而不是父類實例)。

最后我們調用原始的子類方法執(zhí)行自己的工作(也有可能使用了_super)嘶炭,然后將_super恢復成原來的狀態(tài)抱慌,并返回調用結果。

有很多方式可以達到類似的結果(有的實現(xiàn)旱物,會通過訪問arguments.callee遥缕,將_super方法綁定到方法自身),但是該特定技術提供了良好的可用性和簡便性宵呛。

我會在我寫的書中覆蓋更多的JavaScript原型系統(tǒng)背后的真相,我只是想把這個類實現(xiàn)放到這里夕凝,讓每個人都嘗試使用它宝穗。我認為這個簡單的代碼可以說明很多的事情(更容易去學習户秤,去繼承,更少的下載)逮矛,因此我認為這個實現(xiàn)是開始和學習JavaScript類構造和繼承的基礎的好地方鸡号。


其實我是拜讀過忍者秘籍的,這個例子在忍者秘籍中的第六章 - 原型與面向對象中做了更加詳細的講解须鼎,此外本書全面且詳細的講解了javascript的基礎部分(函數(shù)鲸伴、執(zhí)行上下文、閉包等等)晋控,并且書中有部分例子實際上有些深奧汞窗,部分例子在我當初第一次閱讀的時候并沒有完全理解,于是我就把該頁折疊起來赡译,日后再次閱讀理解更為透徹一點仲吏。 由于我是認真閱讀過忍者秘籍的,我認為這本書非常的不錯(畢竟是jQuery作者寫的)蝌焚,因此在這里我向各位初學者推薦這本書裹唆,希望對大家有所幫助。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末只洒,一起剝皮案震驚了整個濱河市许帐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毕谴,老刑警劉巖舞吭,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異析珊,居然都是意外死亡羡鸥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門忠寻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惧浴,“玉大人,你說我怎么就攤上這事奕剃≈月茫” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵纵朋,是天一觀的道長柿顶。 經常有香客問我,道長操软,這世上最難降的妖魔是什么嘁锯? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上家乘,老公的妹妹穿的比我還像新娘蝗羊。我一直安慰自己,他們只是感情好仁锯,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布耀找。 她就那樣靜靜地躺著,像睡著了一般业崖。 火紅的嫁衣襯著肌膚如雪野芒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天双炕,我揣著相機與錄音狞悲,去河邊找鬼。 笑死雄家,一個胖子當著我的面吹牛效诅,可吹牛的內容都是我干的。 我是一名探鬼主播趟济,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乱投,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了顷编?” 一聲冷哼從身側響起戚炫,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎媳纬,沒想到半個月后双肤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡钮惠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年茅糜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片素挽。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔑赘,死狀恐怖,靈堂內的尸體忽然破棺而出预明,到底是詐尸還是另有隱情缩赛,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布撰糠,位于F島的核電站酥馍,受9級特大地震影響,放射性物質發(fā)生泄漏阅酪。R本人自食惡果不足惜旨袒,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一汁针、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧峦失,春花似錦扇丛、人聲如沸术吗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽较屿。三九已至隧魄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隘蝎,已是汗流浹背购啄。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘱么,地道東北人狮含。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像曼振,于是被迫代替她去往敵國和親几迄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內容

  • class的基本用法 概述 JavaScript語言的傳統(tǒng)方法是通過構造函數(shù)冰评,定義并生成新對象映胁。下面是一個例子: ...
    呼呼哥閱讀 4,092評論 3 11
  • 基于這篇文章的一些名稱約定: 上面的約定應該是比較合理的,如果難以理解甲雅,可以查看黯羽輕揚:JS學習筆記2_面向對象...
    一直玩編程閱讀 525評論 1 7
  • 本文先對es6發(fā)布之前javascript各種繼承實現(xiàn)方式進行深入的分析比較解孙,然后再介紹es6中對類繼承的支持以及...
    lazydu閱讀 16,679評論 7 44
  • 創(chuàng)世神話,是先民對“我是誰抛人,我從哪里來弛姜,我要到哪里去”這個人類本質問題的一種解答⊙叮——號稱用科學的方法考察歷史學阿...
    芷汀閱讀 2,979評論 6 10
  • 坡的恐怖魅力在他寫的海洋故事中很突出廷臼。如《大漩渦底余生記》寫一個水手卷入挪威西部美爾斯特羅大漩渦,《瓶中手稿...
    77b9dc450adc閱讀 458評論 0 0