JS 繼承的多種方法

一、原型鏈

學(xué)過java的同學(xué)應(yīng)該都知道,繼承是java的重要特點之一,許多面向?qū)ο蟮恼Z言都支持兩種繼承方式:接口繼承和實現(xiàn)繼承椎木,接口繼承只繼承方法簽名,而實現(xiàn)繼承則繼承實際的方法博烂,在js中香椎,由于函數(shù)沒有簽名,因此支持實現(xiàn)繼承禽篱,而實現(xiàn)繼承主要是依靠原型鏈來實現(xiàn)的畜伐,那么,什么是原型鏈呢躺率?

首先玛界,我們先來回顧一下構(gòu)造函數(shù)万矾,原型和實例之間的關(guān)系。當(dāng)我們創(chuàng)建一個構(gòu)造函數(shù)時慎框,構(gòu)造函數(shù)會獲得一個prototype屬性良狈,該屬性是一個指針,指向一個原型對象笨枯,原型對象包含一個constructor屬性薪丁,該屬性也是一個指針,指向構(gòu)造函數(shù)馅精,而當(dāng)我們創(chuàng)建構(gòu)造函數(shù)的實例時严嗜,該實例其實會獲得一個[[Prototype]]屬性,指向原型對象洲敢。

function SubType() {}
var instance = new SubType();

比如上面的代碼漫玄,其中,SubType是構(gòu)造函數(shù)压彭,SubType.prototype是原型對象睦优,instance是實例,這三者的關(guān)系可以用下面的圖表示壮不。

三者的關(guān)系

而這個時候呢刨秆,如果我們讓原型對象等于另一個構(gòu)造函數(shù)的實例,此時的原型對象就會獲得一個[[Prototype]]屬性忆畅,該屬性會指向另一個原型對象,如果另一個原型對象又是另一個構(gòu)造函數(shù)的實例尸执,這個原型對象又會獲得一個[[Prototype]]屬性家凯,該屬性又會指向另一個原型對象,如此層層遞進如失,就構(gòu)成了實例與原型的鏈條绊诲,這就是原型鏈。

我們再看下上面的例子褪贵,如果這個時候掂之,我們讓SubType.prototype是另一個構(gòu)造函數(shù)的實例,此時會怎么樣呢脆丁?

function SuperType() {}
function SubType() {}
SubType.prototype = new SuperType();
var instance = new SubType();

上面的代碼中世舰,我們先是讓SubType繼承了SuperType,接著創(chuàng)建出SubType的實例instance槽卫,因此跟压,instance可以訪問SubType和SuperType原型上的屬性和方法,也就是實現(xiàn)了繼承歼培,繼承關(guān)系我們可以用下面的圖說明震蒋。

繼承關(guān)系

最后茸塞,要提醒大家的是,所有引用類型默認都繼承了Object查剖,這個繼承也是通過原型鏈實現(xiàn)的钾虐,因此,其實原型鏈的頂層就是Object的原型對象啦笋庄。

二效扫、繼承

上面我們弄清了原型鏈,接下來主要就介紹一些經(jīng)常會用到的繼承方法无切,具體要用哪一種荡短,還是需要依情況而定的。

1. 原型鏈繼承

最常見的繼承方法就是使用原型鏈實現(xiàn)繼承啦哆键,也就是我們上面所介紹的掘托,接下來,還是看一個實際的例子籍嘹。

function SuperType() {
  this.property = true;
}
SuperType.prototype.getSuperValue = function() {
  return this.property;
}
function SubType() {
  ths.subproperty = true;
}
SubType.prototype = new SuperType();// 實現(xiàn)繼承
SubType.prototype.getSubValue = function() {
  return this.subprototype;
}
var instance = new SubType();
console.log(instance.getSuperValue());// true

上面的例子中闪盔,我們沒有使用SubType默認提供的原型,而是給它換了一個新原型辱士,這個新原型就是SuperType的實例泪掀,因此,新原型具有作為SuperType實例所擁有的全部實現(xiàn)和方法颂碘,并且指向SuperType的原型异赫,因此,instance實例具有subproperty屬性头岔,SubType.prototype具有property屬性塔拳,值為true,并且擁有g(shù)etSubValue方法峡竣,而SuperType擁有g(shù)etSuperValue方法靠抑。

當(dāng)調(diào)用instance的getSuperValue()方法時,因此在instance實例上找不到該方法适掰,就會順著原型鏈先找到SubType.prototype颂碧,還是找不到該方法,繼續(xù)順著原型鏈找到SuperType.prototype类浪,終于找到getSuperValue载城,就調(diào)用了該函數(shù),而該函數(shù)返回property费就,該值的查找也是同樣的道理个曙,會在SubType.prototype中找到該屬性,值為true,所以顯示true垦搬。

存在的問題:通過原型鏈實現(xiàn)繼承時呼寸,原型實際上會變成另一個類型實例,而原先的實例屬性也會變成原型屬性猴贰,如果該屬性為引用類型時对雪,所有的實例都會共享該屬性,一個實例修改了該屬性米绕,其它實例也會發(fā)生變化瑟捣,同時,在創(chuàng)建子類型時栅干,我們也不能向超類型的構(gòu)造函數(shù)中傳遞參數(shù)迈套。

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

為了解決原型中包含引用類型值所帶來的問題,開發(fā)人員開始使用借用構(gòu)造函數(shù)的技術(shù)實現(xiàn)繼承碱鳞,該方法主要是通過apply()和call()方法桑李,在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù),從而解決該問題窿给。

function SuperType() {
  this.colors = ["red","blue","green"]
}
function SubType() {
  SuperType.call(this);// 實現(xiàn)繼承
}
var instance1 = new SubType();
var instance2  = new SubType();
instance2.colors.push("black");
console.log(instance1.colors);// red,blue,green
console.log(instance2.colors);// red,blue,green,black

在上面的例子中贵白,如果我們使用原型鏈繼承,那么instance1和instance2將會共享colors屬性崩泡,因為colors屬性存在于SubType.prototype中禁荒,而上面我們使用了借用構(gòu)造函數(shù)繼承,通過使用call()方法角撞,我們實際上是在新創(chuàng)建的SubType實例的環(huán)境下調(diào)用了SuperType的構(gòu)造函數(shù)呛伴,因此,colors屬性是分別存在instance1和instance2實例中的谒所,修改其中一個不會影響另一個磷蜀。

使用這個方法,我們還可以在子類型構(gòu)造函數(shù)中向超類型構(gòu)造函數(shù)傳遞參數(shù)百炬。

function SuperType(name) {
  this.name = name;
}
function SubType() {
  SuperType.call(this,"Nicholas");
  this.age = 29;
}
var instance = new SubType();
console.log(instance.name);// Nicholas
console.log(instance.age);// 29

優(yōu)點:解決了原型鏈繼承中引用類型的共享問題,同時可以在子類型構(gòu)造函數(shù)中向超類型構(gòu)造函數(shù)傳遞參數(shù)污它。
缺點:定義方法時剖踊,將會在每個實例上都會重新定義,不能實現(xiàn)函數(shù)的復(fù)用衫贬。

3. 組合繼承

組合繼承主要是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊德澈,從而發(fā)貨兩者之長的一種繼承模式,主要是使用原型鏈實現(xiàn)對原型屬性和方法的基礎(chǔ)固惯,通過借用構(gòu)造函數(shù)實現(xiàn)對實例屬性的基礎(chǔ)梆造,這樣,可以通過在原型上定義方法實現(xiàn)函數(shù)的復(fù)用,又能夠保證每個實例都有自己的屬性镇辉。

function SuperType(name) {
  this.name = name;
  this.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
}
function SubType(name,age) {
  SuperType.call(this,name);
  this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
  console.log(this.age);
}

var instance1 = new SubType("Nicholas", 29);
var instance2 =new SubType("Greg", 27);
instance1.colors.push("black");
console.log(instance1.colors); // red,blue,green,black
console.log(instance2.colors); // red,blue,green
instance1.sayName(); // Nicholas
instance2.sayName(); // 29
instance1.sayAge(); // Greg
instance2.sayAge(); // 27 

組合繼承避免了原型鏈和借用構(gòu)造函數(shù)的缺陷屡穗,融合了它們的優(yōu)點,現(xiàn)在已經(jīng)成為js中最常用的繼承方法忽肛。

缺點:無論什么情況下村砂,都會調(diào)用兩次超類型構(gòu)造函數(shù),一次是在創(chuàng)建子類型的時候屹逛,另一次是在子類型構(gòu)造函數(shù)內(nèi)部础废,子類型最終會包含超類型對象的全部實例屬性,但是需要在調(diào)用子類型構(gòu)造函數(shù)時重寫這些屬性罕模。

4. 原型式繼承

原型式繼承主要的借助原型可以基于已有的對象創(chuàng)建新的對象评腺,基本思想就是創(chuàng)建一個臨時性的構(gòu)造函數(shù),然后將傳入的對象作為這個構(gòu)造函數(shù)的原型淑掌,最后返回這個臨時類型的一個新實例蒿讥。

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

從上面的例子我們可以看出,如果我們想創(chuàng)建一個對象锋拖,讓它繼承另一個對象的話诈悍,就可以將要被繼承的對象當(dāng)做o傳遞到Object函數(shù)里面去,Object函數(shù)里面返回的將會是一個新的實例兽埃,并且這個實例繼承了o對象侥钳。

其實,如果我們要使用原型式繼承的話柄错,可以直接通過Object.create()方法來實現(xiàn)芙粱,這個方法接收兩個參數(shù),第一個參數(shù)是用作新對象原型的對象伍宦,第二個參數(shù)是一個為新對象定義額外屬性的對象丙号,一般來說,第二個參數(shù)可以省略颂跨。

var person = {
  name: "Nicholas",
  friends: ["Shelby","Court","Van"]
}
var anotherPerson = Object.create(person, {
  name: {
    value: "Greg"
  }
});
console.log(anotherPerson.name); // Greg

上面的例子中敢伸,我們讓anotherPerson繼承了person,其中恒削,friends作為引用類型池颈,將會被所有繼承該對象的對象所共享,而通過傳入第二個參數(shù)钓丰,我們可以定義額外的屬性躯砰,修改person中的原有信息。

缺點:原型式繼承中包含引用類型的屬性始終都會共享相應(yīng)的值携丁。

5. 寄生式繼承

寄生式繼承其實和我們前面說的創(chuàng)建對象方法中的寄生構(gòu)造函數(shù)和工程模式很像琢歇,創(chuàng)建一個僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部以某種方法來增強對象,最后再返回該對象李茫。

function createAnother(original) {
  var clone = Object(original);      
  // 通過調(diào)用函數(shù)創(chuàng)建一個新對象
  clone.sayHi = function() {
    console.log("hi");
  }
  return clone;
}

我們其實可以把寄生式繼承看做是傳進去一個對象揭保,然后對該對象進行一定的加工,也就是增加一些方法來增強該對象涌矢,然后再返回一個包含新方法的對象的一個過程掖举。

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

從上面的代碼中我們可以看出,原來person是沒有包含任何方法的娜庇,而通過將person傳進去createAnother方法中進行加工塔次,返回的新對象就包含了一個新的方法。

缺點:不能實現(xiàn)函數(shù)的復(fù)用名秀。

6. 寄生組合式繼承

組合繼承是js中最經(jīng)常用到的一種繼承方法励负,而我們前面也已經(jīng)說了組合繼承的缺點,組合繼承需要調(diào)用兩次超類型構(gòu)造函數(shù)匕得,一次是在創(chuàng)建子類型原型的時候继榆,另一次是在子類型構(gòu)造函數(shù)內(nèi)部,子類型最終會包含超類型對象的全部實例屬性汁掠,但是我們不得不在調(diào)用子類型構(gòu)造函數(shù)時重寫這些屬性略吨。

function SuperType(name) {
  this.name = name;
  this.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
}
function SubType(name,age) {
  SuperType.call(this,name); // 第二次調(diào)用超類型構(gòu)造函數(shù)
  this.age = age;
}
SubType.prototype = new SuperType(); // 第一次調(diào)用超類型構(gòu)造函數(shù)
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
  console.log(this.age);
}

上面的代碼中有兩次調(diào)用了超類型構(gòu)造函數(shù),那兩次調(diào)用會帶來什么結(jié)果呢考阱?結(jié)果就是在SubType.prototype和SubType的實例上都會創(chuàng)建name和colors屬性翠忠,最后SubType的實例上的name和colors屬性會屏蔽掉SubType.prototype上的name和colors屬性。

寄生組合式繼承就是可以解決上面這個問題乞榨,寄生組合式繼承主要通過借用構(gòu)造函數(shù)來繼承屬性秽之,通過原型鏈的混成形式來繼承方法,其實就是不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù)吃既,只需要超類型原型的一個副本就可以了考榨。

function inheritPrototype(subType,SuperType) {
  var prototype = Object(SuperType); // 創(chuàng)建對象
  prototype.constructor = subType; // 增強對象
  subType.prototype = prototype; // 指定對象
}

在上面的例子中,第一步創(chuàng)建了超類型原型的一個副本鹦倚,第二步為創(chuàng)建的副本添加constructor屬性河质,從而彌補因重寫原型而失去的默認的constructor屬性,最后一步將副本也就是新對象賦值給子類型的原型震叙,因此掀鹅,我們可以用這個函數(shù)去替換前面說到為子類型原型賦值的語句。

function SuperType(name) {
  this.name = name;
  this.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
}
function SubType(name,age) {
  SuperType.call(this,name);
  this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function() {
  console.log(this.age);
}

寄生組合式繼承只調(diào)用了一次SuperType構(gòu)造函數(shù)捐友,避免了在SubType.prototype上面創(chuàng)建的不必要的,多余的屬性溃槐,現(xiàn)在也是很多人使用這種方法實現(xiàn)繼承啦匣砖。

7. es6中的繼承

我們在前面創(chuàng)建對象中也提到了es6中可以使用Class來創(chuàng)建對象,而同樣的道理,在es6中猴鲫,也新增加了extends實現(xiàn)Class的繼承对人,Class 可以通過extends關(guān)鍵字實現(xiàn)繼承,這比 ES5 的通過修改原型鏈實現(xiàn)繼承拂共,要清晰和方便很多牺弄。

class Point {}
class ColorPoint extends Point {}

上面這個例子中可以實現(xiàn)ColorPoint類繼承Point類,這種簡潔的語法確實比我們上面介紹的那些方法要簡潔的好多呀宜狐。

但是呢势告,使用extends實現(xiàn)繼承的時候,還是有幾點需要注意的問題抚恒,子類在繼承父類的時候咱台,子類必須在constructor方法中調(diào)用super方法,否則新建實例時會報錯俭驮,這是因為子類自己的this對象回溺,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實例屬性和方法混萝,然后再對其進行加工遗遵,加上子類自己的實例屬性和方法,如果不調(diào)用super方法逸嘀,子類就得不到this對象车要。

class Point {  
  constructor(x,  y) {    
    this.x = x;    
    this.y = y;  
  }
}
class ColorPoint extends Point {  
  constructor(x,  y,  color) {    
    this.color = color; // ReferenceError    
    super(x, y);    
    this.color = color; // 正確  
  }
}

上面代碼中,子類的constructor方法沒有調(diào)用super之前厘熟,就使用this關(guān)鍵字屯蹦,結(jié)果報錯,而放在super方法之后就是正確的绳姨,正確的繼承之后登澜,我們就可以創(chuàng)建實例了。

let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint; // true
cp instanceof Point; // true

看完文章飘庄,還有福利拿哦脑蠕,往下看??????
感興趣的小伙伴可以在公號【grain先森】后臺回復(fù)【190315】獲取【CSS 參考規(guī)范】,可以轉(zhuǎn)發(fā)朋友圈和你的朋友分享哦跪削。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谴仙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子碾盐,更是在濱河造成了極大的恐慌晃跺,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毫玖,死亡現(xiàn)場離奇詭異掀虎,居然都是意外死亡凌盯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門烹玉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驰怎,“玉大人,你說我怎么就攤上這事二打∠丶桑” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵继效,是天一觀的道長症杏。 經(jīng)常有香客問我,道長莲趣,這世上最難降的妖魔是什么鸳慈? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮喧伞,結(jié)果婚禮上走芋,老公的妹妹穿的比我還像新娘。我一直安慰自己潘鲫,他們只是感情好翁逞,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溉仑,像睡著了一般挖函。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浊竟,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天怨喘,我揣著相機與錄音,去河邊找鬼振定。 笑死必怜,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的后频。 我是一名探鬼主播梳庆,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卑惜!你這毒婦竟也來了膏执?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤露久,失蹤者是張志新(化名)和其女友劉穎更米,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毫痕,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡征峦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年纸巷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眶痰。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖梯啤,靈堂內(nèi)的尸體忽然破棺而出竖伯,到底是詐尸還是另有隱情,我是刑警寧澤因宇,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布七婴,位于F島的核電站,受9級特大地震影響察滑,放射性物質(zhì)發(fā)生泄漏打厘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一贺辰、第九天 我趴在偏房一處隱蔽的房頂上張望户盯。 院中可真熱鬧,春花似錦饲化、人聲如沸莽鸭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硫眨。三九已至,卻和暖如春巢块,著一層夾襖步出監(jiān)牢的瞬間礁阁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工族奢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姥闭,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓歹鱼,卻偏偏與公主長得像泣栈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子弥姻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

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