JavaScript的繼承

前言

目前JavaScript的繼承方式有以下幾種:原型鏈繼承起愈,構(gòu)造函數(shù)繼承用狱,組合繼承,原型式繼承灵巧,寄生式繼承搀矫,寄生組合式繼承,ES6類繼承extends


image.png

1.原型鏈繼承

構(gòu)造函數(shù)刻肄、原型和實(shí)例之間的關(guān)系:每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象瓤球,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個(gè)原型對(duì)象的指針敏弃。

function SuperType() {
    this.property = true;
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
}

function SubType() {
    this.subproperty = false;
}

// 這里是關(guān)鍵卦羡,創(chuàng)建SuperType的實(shí)例,并將該實(shí)例賦值給SubType.prototype
SubType.prototype = new SuperType(); 

SubType.prototype.getSubValue = function() {
    return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue()); // true

image.png

優(yōu)點(diǎn):

  • 父類方法可以復(fù)用
    缺點(diǎn):
  • 父類的引用屬性會(huì)被所有子類實(shí)例共享麦到,多個(gè)實(shí)例對(duì)引用類型的操作會(huì)被篡改
  • 子類構(gòu)建實(shí)例時(shí)不能向父類傳遞參數(shù)

2.構(gòu)造函數(shù)繼承

將父類構(gòu)造函數(shù)的內(nèi)容復(fù)制給了子類的構(gòu)造函數(shù)绿饵。這是所有繼承中唯一一個(gè)不涉及到prototype的繼承。核心代碼是SuperType.call(this)瓶颠,創(chuàng)建子類實(shí)例時(shí)調(diào)用SuperType構(gòu)造函數(shù)拟赊,于是SubType的每個(gè)實(shí)例都會(huì)將SuperType中的屬性復(fù)制一份。

function  SuperType(){
    this.color=["red","green","blue"];
}
function  SubType(){
    //繼承自SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"

var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"

優(yōu)點(diǎn):

  • 父類的引用屬性不會(huì)被共享
  • 子類構(gòu)建實(shí)例時(shí)可以向父類傳遞參數(shù)
    缺點(diǎn):
  • 只能繼承父類的實(shí)例屬性和方法步清,不能繼承原型屬性/方法
  • 父類的方法不能復(fù)用要门,子類實(shí)例的方法每次都是單獨(dú)創(chuàng)建的

3.組合繼承

組合上述兩種方法就是組合繼承虏肾。用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,用借用構(gòu)造函數(shù)技術(shù)來實(shí)現(xiàn)實(shí)例屬性的繼承欢搜。

function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

function SubType(name, age){
  // 繼承屬性
  // 第二次調(diào)用SuperType()
  SuperType.call(this, name);
  this.age = age;
}

// 繼承方法
// 構(gòu)建原型鏈
// 第一次調(diào)用SuperType()
SubType.prototype = new SuperType(); 
// 重寫SubType.prototype的constructor屬性封豪,指向自己的構(gòu)造函數(shù)SubType
SubType.prototype.constructor = SubType; 
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
image.png

優(yōu)點(diǎn):

  • 父類的方法可以被復(fù)用
  • 父類的引用屬性不會(huì)被共享
  • 子類構(gòu)建實(shí)例時(shí)可以向父類傳遞參數(shù)
    缺點(diǎn):
  • 第一次調(diào)用SuperType():給SubType.prototype寫入兩個(gè)屬性name,color炒瘟。
  • 第二次調(diào)用SuperType():給instance1寫入兩個(gè)屬性name吹埠,color。
  • 實(shí)例對(duì)象instance1上的兩個(gè)屬性就屏蔽了其原型對(duì)象SubType.prototype的兩個(gè)同名屬性疮装。所以缘琅,組合模式的缺點(diǎn)就是在使用子類創(chuàng)建實(shí)例對(duì)象時(shí),其原型中會(huì)存在兩份相同的屬性/方法廓推。

4.原型式繼承

利用一個(gè)空對(duì)象作為中介刷袍,將某個(gè)對(duì)象直接賦值給空對(duì)象構(gòu)造函數(shù)的原型。原型式繼承的object方法本質(zhì)上是對(duì)參數(shù)對(duì)象的一個(gè)淺復(fù)制樊展。

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

object()對(duì)傳入其中的對(duì)象執(zhí)行了一次淺復(fù)制呻纹,將構(gòu)造函數(shù)F的原型直接指向傳入的對(duì)象。另外专缠,ES5中存在Object.create()的方法雷酪,能夠代替上面的object方法。

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");

alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

優(yōu)點(diǎn):

  • 父類方法可以復(fù)用
    缺點(diǎn):
  • 父類的引用屬性會(huì)被所有子類實(shí)例共享
  • 子類構(gòu)建實(shí)例時(shí)不能向父類傳遞參數(shù)

5.寄生式繼承

核心:在原型式繼承的基礎(chǔ)上涝婉,增強(qiáng)對(duì)象哥力,返回構(gòu)造函數(shù)

function createAnother(original){
  var clone = object(original); // 通過調(diào)用 object() 函數(shù)創(chuàng)建一個(gè)新對(duì)象
  clone.sayHi = function(){  // 以某種方式來增強(qiáng)對(duì)象
    alert("hi");
  };
  return clone; // 返回這個(gè)對(duì)象
}

函數(shù)的主要作用是為構(gòu)造函數(shù)新增屬性和方法,以增強(qiáng)函數(shù)

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

缺點(diǎn)(同原型式繼承):

  • 原型鏈繼承多個(gè)實(shí)例的引用類型屬性指向相同墩弯,存在篡改的可能吩跋。
  • 無法傳遞參數(shù)

6.寄生組合式繼承

結(jié)合借用構(gòu)造函數(shù)傳遞參數(shù)和寄生模式實(shí)現(xiàn)繼承,這是最成熟的方法渔工,也是現(xiàn)在庫實(shí)現(xiàn)的方法钞澳。

function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype); // 創(chuàng)建對(duì)象,創(chuàng)建父類原型的一個(gè)副本
  prototype.constructor = subType;                    // 增強(qiáng)對(duì)象涨缚,彌補(bǔ)因重寫原型而失去的默認(rèn)的constructor 屬性
  subType.prototype = prototype;                      // 指定對(duì)象,將新創(chuàng)建的對(duì)象賦值給子類的原型
}

// 父類初始化實(shí)例屬性和原型屬性
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

// 借用構(gòu)造函數(shù)傳遞增強(qiáng)子類實(shí)例屬性(支持傳參和避免篡改)
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}

// 將父類原型指向子類
inheritPrototype(SubType, SuperType);

// 新增子類原型屬性
SubType.prototype.sayAge = function(){
  alert(this.age);
}

var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);

instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
image.png

這個(gè)例子的高效率體現(xiàn)在它只調(diào)用了一次SuperType 構(gòu)造函數(shù)策治,并且因此避免了在SubType.prototype 上創(chuàng)建不必要的脓魏、多余的屬性。于此同時(shí)通惫,原型鏈還能保持不變茂翔;因此,還能夠正常使用instanceof() 和isPrototypeOf()

7.ES6類繼承extends

extends關(guān)鍵字主要用于類聲明或者類表達(dá)式中履腋,以創(chuàng)建一個(gè)類珊燎,該類是另一個(gè)類的子類惭嚣。其中constructor表示構(gòu)造函數(shù),一個(gè)類中只能有一個(gè)構(gòu)造函數(shù)悔政,有多個(gè)會(huì)報(bào)出SyntaxError錯(cuò)誤,如果沒有顯式指定構(gòu)造方法晚吞,則會(huì)添加默認(rèn)的 constructor方法,使用例子如下:

class Rectangle {
    // constructor
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
    
    // Getter
    get area() {
        return this.calcArea()
    }
    
    // Method
    calcArea() {
        return this.height * this.width;
    }
}

const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 輸出 200

-----------------------------------------------------------------
// 繼承
class Square extends Rectangle {

  constructor(length) {
    super(length, length);
    
    // 如果子類中存在構(gòu)造函數(shù)谋国,則需要在使用“this”之前首先調(diào)用 super()槽地。
    this.name = 'Square';
  }

  get area() {
    return this.height * this.width;
  }
}

const square = new Square(10);
console.log(square.area);
// 輸出 100

extends繼承的核心代碼如下,其實(shí)現(xiàn)和上述的寄生組合式繼承方式一樣

function _inherits(subType, superType) {
  
    // 創(chuàng)建對(duì)象芦瘾,創(chuàng)建父類原型的一個(gè)副本
    // 增強(qiáng)對(duì)象捌蚊,彌補(bǔ)因重寫原型而失去的默認(rèn)的constructor 屬性
    // 指定對(duì)象,將新創(chuàng)建的對(duì)象賦值給子類的原型
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: {
            value: subType,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    
    if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
    }
}

參考文章

https://juejin.im/post/6844903696111763470#heading-0

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末近弟,一起剝皮案震驚了整個(gè)濱河市缅糟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祷愉,老刑警劉巖窗宦,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異谣辞,居然都是意外死亡迫摔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門泥从,熙熙樓的掌柜王于貴愁眉苦臉地迎上來句占,“玉大人,你說我怎么就攤上這事躯嫉∩春妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵祈餐,是天一觀的道長(zhǎng)擂啥。 經(jīng)常有香客問我,道長(zhǎng)帆阳,這世上最難降的妖魔是什么哺壶? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蜒谤,結(jié)果婚禮上山宾,老公的妹妹穿的比我還像新娘。我一直安慰自己鳍徽,他們只是感情好资锰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阶祭,像睡著了一般绷杜。 火紅的嫁衣襯著肌膚如雪直秆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天鞭盟,我揣著相機(jī)與錄音圾结,去河邊找鬼。 笑死懊缺,一個(gè)胖子當(dāng)著我的面吹牛疫稿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鹃两,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼遗座,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了俊扳?” 一聲冷哼從身側(cè)響起途蒋,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馋记,沒想到半個(gè)月后号坡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梯醒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年宽堆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茸习。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畜隶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出号胚,到底是詐尸還是另有隱情籽慢,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布猫胁,位于F島的核電站箱亿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏弃秆。R本人自食惡果不足惜届惋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菠赚。 院中可真熱鬧盼樟,春花似錦、人聲如沸锈至。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽峡捡。三九已至击碗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間们拙,已是汗流浹背稍途。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留砚婆,地道東北人械拍。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像装盯,于是被迫代替她去往敵國(guó)和親坷虑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354