js繼承

原文鏈接

js的繼承有6種方式亡驰,大致總結(jié)一下它們各自的優(yōu)缺點,以及它們之間的關(guān)系嗡贺。


1.原型鏈

js的繼承機制不同于傳統(tǒng)的面向?qū)ο笳Z言隐解,采用原型鏈實現(xiàn)繼承,基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法诫睬。理解原型鏈必須先理解原型煞茫,以下是對于原型的一些解釋:

無論什么時候,只要創(chuàng)建了一個新函數(shù)摄凡,就會根據(jù)一組特定規(guī)則為該函數(shù)創(chuàng)建一個prototype屬性续徽。這個屬性指向函數(shù)的原型對象,所有原型對象都會自動獲得一個constructor屬性亲澡,這個屬性是一個指向prototype屬性所在函數(shù)的指針钦扭。創(chuàng)建自定義的構(gòu)造函數(shù)之后,其原型對象只會取得constructor屬性床绪,其他方法都是從Object繼承來的客情。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新實例之后,該實例的內(nèi)部包含一個指針癞己,指向構(gòu)造函數(shù)的原型對象膀斋,即[[Prototype]],在瀏覽器中為_proto_

也就是說痹雅,構(gòu)造函數(shù)和實例實際上都是存在一個指向原型的指針仰担,構(gòu)造函數(shù)指向原型的指針為其prototype屬性。實例也包含一個不可訪問的指針[[Prototype]](實際在瀏覽器中可以用_proto_訪問)绩社,而原型鏈的形成真正依賴的是_proto_而非[[Prototype]]摔蓝。

舉個例子

下邊是一個最簡單的繼承方式的例子:用父類實例充當(dāng)子類原型對象。

function SuperType(){                        
    this.property = true;
    this.arr = [1];
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
SubType.prototype = new SuperType();              
//在此繼承愉耙,SubType的prototype為SuperType的一個實例
SubType.prototype.getSubValue = function(){
    return this.subproperty;
};
var instance = new SubType();
var instance2 = new SubType();                                   
c(instance.getSuperValue());                      //true
c(instance.getSubValue());                        //false
c(instance.__proto__.prototype);                  //undefined
//SubType繼承了SuperType贮尉,SuperType繼承了Object。
//instance的_proto_是SubType的原型對象朴沿,即SubType.prototype猜谚。
//而SubType.prototype又是SuperType的一個實例。
//則instance._proto_.prototype為undefined悯仙,
//因為SuperType的實例對象不包含prototype屬性龄毡。
instance.arr.push(2);
c(instance.arr);                                  //[1,2]
c(instance2.arr);                                 //[1,2]
//子類們共享引用屬性

需要注意的一點:無論以什么方式繼承吠卷,請謹慎使用將對象字面量賦值給原型的方法锡垄,這樣會重寫原型鏈。

優(yōu)缺點

原型鏈繼承方式的優(yōu)點在于簡單祭隔,而缺點也十分致命:

  1. 子類之間會共享引用類型屬性
  2. 創(chuàng)建子類時货岭,無法向父類構(gòu)造函數(shù)傳參

2.借用構(gòu)造函數(shù)

又叫經(jīng)典繼承路操,借用構(gòu)造函數(shù)繼承的主要思想:在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù),即用call()apply()方法給子類中的this執(zhí)行父類的構(gòu)造函數(shù)千贯,使其擁有父類擁有的屬性實現(xiàn)繼承屯仗,這種繼承方法完全沒有用到原型。下邊是借用構(gòu)造函數(shù)的實現(xiàn):

function SuperType(){                                 
    this.colors = ["red","blue","green"];
}
function SubType(){
    SuperType.call(this);     //借用構(gòu)造函數(shù)
}
var instance1 = new SubType();
instance1.colors.push("black");
c(instance1.colors);          //["red","blue","green","black"]
var instance2 = new SubType();
c(instance2.colors);          //["red","blue","green"]

舉個例子

借用構(gòu)造函數(shù)搔谴,相當(dāng)于將父類擁有的屬性在子類的構(gòu)造函數(shù)也寫了一遍魁袜,使子類擁有父類擁有的屬性,這種方法在創(chuàng)建子類實例時敦第,可以向父類構(gòu)造函數(shù)傳遞參數(shù) 峰弹。

function SuperType(name){           
    this.name = name;
}
function SubType(name){
    SuperType.call(this,name);          //借用構(gòu)造函數(shù)模式傳遞參數(shù)
    this.age = 29;
}
var instance = new SubType("something");
c(instance.name);                       //something
c(instance.age);                        //29

優(yōu)缺點

借用構(gòu)造函數(shù)模式,不同于原型式繼承和原型模式芜果,它不會共享引用類型屬性鞠呈,而且也可以向超類型構(gòu)造函數(shù)傳遞參數(shù)。但是相對的右钾,由于不會共享屬性蚁吝,也無法實現(xiàn)代碼復(fù)用,相同的函數(shù)在每個實例中都有一份舀射。為了實現(xiàn)代碼復(fù)用窘茁,提示效率,大神們又想出了下邊的繼承方法后控。


3.組合繼承

組合繼承有時也叫偽經(jīng)典繼承庙曙,是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮二者之長的一種繼承模式浩淘。

即用原型鏈實現(xiàn)對原型屬性和方法的繼承(需要共享的)捌朴,通過借用構(gòu)造函數(shù)實現(xiàn)對實例屬性的繼承(不共享的)。這樣的方法實現(xiàn)了函數(shù)復(fù)用张抄,而且每個實例擁有自己的屬性砂蔽。

舉個例子

function SuperType(name) {                       //父類的實例屬性
    this.name = name;
    this.colors = ["red", "blue", "green"];      
}
SuperType.prototype.sayName = function() {       //父類的原型屬性
    c(this.name);
};

function SubType(name, age) {                    //借用構(gòu)造函數(shù)繼承實例屬性
    SuperType.call(this, name);
    this.age = age;
}
SubType.prototype = new SuperType();             //原型鏈繼承原型屬性
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    c(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
c(instance1.colors);        //"red,blue,green,black"
delete instance1.colors;    
//刪除從實例屬性繼承來的colors,讀取colors會成為從原型繼承來的實例屬性
c(instance1.colors);        //"red,blue,green"
instance1.sayName();        //Nicholas
instance1.sayAge();         //29
var instance2 = new SubType("Greg", 27);
c(instance2.colors);        //"red,blue,green"
instance2.sayName();        //Greg
instance2.sayAge();         //27

優(yōu)缺點

這是所有繼承方式中最常用的署惯,它的優(yōu)點也十分明顯:

  1. 可以在創(chuàng)建子類實例時向父類構(gòu)造函數(shù)傳參左驾。
  2. 引用類型屬性的值可以不共享。
  3. 可以實現(xiàn)代碼復(fù)用极谊,即可以共享相同的方法诡右。

但是這種方法依然有一點不足,調(diào)用了兩次父類的構(gòu)造函數(shù)轻猖,最后會講到一種理論上接近完美的繼承方式帆吻,即寄生組合式繼承。


4.原型式繼承

原型式繼承借助原型基于已有對象創(chuàng)建新對象咙边,需要一個對象作為另一個對象的基礎(chǔ):

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

上邊段代碼就是原型式繼承的核心代碼猜煮,先創(chuàng)建一個臨時性的構(gòu)造函數(shù)次员,然后將傳入的對象作為這個構(gòu)造函數(shù)的原型,最后返回這個臨時類型的一個新實例王带。

舉個例子

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);      //在此繼承
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
c(person.friends);              //["Shelby","Court","Van","Rob","Barbie"]
c(person.name);                 //Nicholas
c(anotherPerson.name);          //Greg
c(yetAnotherPerson.name);       //Linda
delete yetAnotherPerson.name;   
//刪除子類的屬性淑蔚,就會解除對父類屬性的屏蔽,暴露出父類的name屬性
c(yetAnotherPerson.name);       //Nicholas

從上邊的代碼顯示愕撰,由object(注意首字母小寫刹衫,不是對象的構(gòu)造函數(shù))產(chǎn)生的兩個子類會共享父類的引用屬性,其中friends數(shù)組是共享的搞挣,anotherPerson和yetAnotherPerson都是繼承自person绪妹。實際上相當(dāng)于創(chuàng)建了兩個person對象的副本,但可以在產(chǎn)生之后擁有各自的實例屬性柿究。

ECMAScript5新增了Object.create()方法規(guī)范化了原型式繼承邮旷,這個方法接受兩個參數(shù):

  1. 一個作為新對象原型的對象(可以是對象或者null)
  2. 另一個為新對象定義額外屬性的對象(可選,這個參數(shù)的格式和Object.defineProperties()方法的第二個參數(shù)格式相同蝇摸,每個屬性都是通過自己的描述符定義的)

下邊是一個例子:

var person = {                      //原型式繼承規(guī)范化為create()函數(shù)
    name: "Nicholas",
    friends: ["Shelby","Court","Van"]
};
var anotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});
c(anotherPerson.name);             //"Greg"

優(yōu)缺點

如果想讓一個對象與另一個對象保持類似婶肩,原型式繼承是很貼切的,但是與原型模式一樣貌夕,包含引用類型的值得屬性會共享相應(yīng)的值律歼。


5.寄生式繼承

寄生式繼承與原型式繼承緊密相關(guān)的一種思路,與寄生構(gòu)造函數(shù)和工廠模式類似啡专,即創(chuàng)建一個僅用于封裝繼承過程的函數(shù)险毁,函數(shù)內(nèi)部以某種方式來增強對象,最后再像真的做了所有工作一樣返回對象们童。

舉個例子

function createAnother(original) { 
    var clone = object(original);          //此處用到了原型式繼承
    clone.sayHi = function() {
        c("Hi");
    };
    return clone;
}
var person = {                             //父類實例
    name: "Nicholas",
    friends: ["Shelby","Court","Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();

上邊的寄生式繼承用到了原型式繼承畔况,向?qū)崿F(xiàn)繼承的函數(shù)傳入一個父類對象實例,再用原型式繼承得到一個父類對象實例的副本慧库,再給這個副本添加屬性跷跪,即增強這個對象,最后返回這個副本對象齐板。由于用到了原型式繼承吵瞻,這個對象的原型指向傳入的父類對象實例。上邊例子用到的object()函數(shù)(原型式繼承)并不是必須的甘磨,任何能夠返回新對象的函數(shù)都適用于寄生式繼承模式橡羞。

優(yōu)缺點

寄生式繼承在主要考慮對象而不是創(chuàng)建自定義類型和構(gòu)造函數(shù)時,是十分有用的济舆。但是如果考慮到用寄生式繼承為對象添加函數(shù)等卿泽,由于沒有用到原型,做不到函數(shù)復(fù)用吗冤,會導(dǎo)致效率降低又厉。


6.寄生組合式繼承

這個名字并不是很貼切,雖然叫寄生組合式繼承椎瘟,但是和寄生式繼承關(guān)系不是很大覆致,主要是用原型式繼承來實現(xiàn)原型屬性的繼承,用借用構(gòu)造函數(shù)模式繼承實例屬性肺蔚。寄生組合式繼承和組合繼承的區(qū)別在于:

  1. 在繼承原型屬性時煌妈,組合繼承用原型鏈繼承了整個父類(通過將父類實例賦值給子類構(gòu)造函數(shù)的原型對象來實現(xiàn)),這使子類中多了一份父類的實例屬性宣羊。而寄生組合式繼承用原型式繼承只繼承了父類的原型屬性(把父類構(gòu)造函數(shù)的原型對象用原型式繼承復(fù)制給子類的構(gòu)造函數(shù)的原型對象)璧诵。
  2. 組合繼承調(diào)用了兩次超類型構(gòu)造函數(shù),寄生組合式繼承調(diào)用了一次仇冯。

舉個例子

function inheritPrototype(subType, superType) {             //寄生式繼承
    var prototype = Object.create(superType.prototype);     //創(chuàng)建對象
    prototype.constructor = subType;                        //增強對象
    subType.prototype = prototype;                          //指定對象
}
function SuperType(name) {
    this.name = name;
    this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
    c(this.name);
};
function SubType(name, age) {
    SuperType.call(this, name);                 //借用構(gòu)造函數(shù)
    this.age = age;                             //添加子類獨有的屬性
}
inheritPrototype(SubType, SuperType);           //此處調(diào)用實現(xiàn)寄生組合繼承的函數(shù)
SubType.prototype.sayAge = function() {         //添加子類獨有原型屬性
    c(this.age);
};
var son = new SubType("erzi",16);
var father = new SuperType("baba");
c(typeof father.sayName);                       //function
c(typeof father.sayAge);                        //SubType獨有的方法之宿,返回undefined
SubType.prototype.sayName = function() {        
    c("This function has be changed");          
}   
//更改子類的方法只會影響子類,prototype是對象,添加新屬性和更改屬性不會影響父類的prototype
father.sayName();                               //baba
son.sayName();                                  //This function has be changed
SuperType.prototype.sayName = function() {      //更改父類的原型屬性
    c("This function has be changed");
}
father.sayName();                               //This function has be changed
son.sayName();                                  //This function has be changed

優(yōu)缺點

這種繼承方式理論上是完美的苛坚,但是由于出現(xiàn)的較晚比被,人們大多數(shù)使用的是組合繼承模式。


以上就是我對于js繼承的一些理解泼舱,如果你有不一樣的想法歡迎討論等缀。

參考資料:《JavaScript高級程序設(shè)計》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市娇昙,隨后出現(xiàn)的幾起案子尺迂,更是在濱河造成了極大的恐慌,老刑警劉巖冒掌,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件噪裕,死亡現(xiàn)場離奇詭異,居然都是意外死亡股毫,警方通過查閱死者的電腦和手機州疾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來皇拣,“玉大人严蓖,你說我怎么就攤上這事⊙跫保” “怎么了颗胡?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吩坝。 經(jīng)常有香客問我毒姨,道長,這世上最難降的妖魔是什么钉寝? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任弧呐,我火速辦了婚禮闸迷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俘枫。我一直安慰自己腥沽,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布鸠蚪。 她就那樣靜靜地躺著今阳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茅信。 梳的紋絲不亂的頭發(fā)上盾舌,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音蘸鲸,去河邊找鬼妖谴。 笑死,一個胖子當(dāng)著我的面吹牛酌摇,可吹牛的內(nèi)容都是我干的窖维。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼妙痹,長吁一口氣:“原來是場噩夢啊……” “哼铸史!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起怯伊,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤琳轿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耿芹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崭篡,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年吧秕,在試婚紗的時候發(fā)現(xiàn)自己被綠了琉闪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡砸彬,死狀恐怖颠毙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砂碉,我是刑警寧澤蛀蜜,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站增蹭,受9級特大地震影響滴某,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一霎奢、第九天 我趴在偏房一處隱蔽的房頂上張望户誓。 院中可真熱鬧,春花似錦幕侠、人聲如沸帝美。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至硕旗,卻和暖如春窗骑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漆枚。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工创译, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墙基。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓软族,卻偏偏與公主長得像,于是被迫代替她去往敵國和親残制。 傳聞我的和親對象是個殘疾皇子立砸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 博客內(nèi)容:什么是面向?qū)ο鬄槭裁匆嫦驅(qū)ο竺嫦驅(qū)ο缶幊痰奶匦院驮瓌t理解對象屬性創(chuàng)建對象繼承 什么是面向?qū)ο?面向?qū)ο?..
    _Dot912閱讀 1,422評論 3 12
  • 我們都曾面臨著選擇,甚至在這個時間初茶,這個我正提筆寫著什么的時候颗祝,就有成千上萬的人要去選擇∧詹迹“選擇”會讓我們走上不同...
    you美動聽話語錄閱讀 130評論 0 0
  • 雨 很大 被雷驚醒 或是未曾睡著 天災(zāi)人禍 老天爺只是把該收的人收走 誰也不知道誰是該走的人 只是累的想走的無可奈...
    三月品閱讀 219評論 0 0