溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JS怎樣實(shí)現(xiàn)繼承

發(fā)布時(shí)間:2021-09-10 18:24:08 來(lái)源:億速云 閱讀:120 作者:柒染 欄目:web開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)JS怎樣繼承,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

原型

繼承得靠原型來(lái)實(shí)現(xiàn),當(dāng)然原型不是這篇文章的重點(diǎn),我們來(lái)復(fù)習(xí)一下即可。
其實(shí)原型的概念很簡(jiǎn)單:

  1. 所有對(duì)象都有一個(gè)屬性 __proto__ 指向一個(gè)對(duì)象,也就是原型

  2. 每個(gè)對(duì)象的原型都可以通過 constructor 找到構(gòu)造函數(shù),構(gòu)造函數(shù)也可以通過 prototype 找到原型

  3. 所有函數(shù)都可以通過 __proto__ 找到 Function 對(duì)象

  4. 所有對(duì)象都可以通過 __proto__ 找到 Object 對(duì)象

  5. 對(duì)象之間通過 __proto__ 連接起來(lái),這樣稱之為原型鏈。當(dāng)前對(duì)象上不存在的屬性可以通過原型鏈一層層往上查找,直到頂層 Object 對(duì)象

其實(shí)原型中最重要的內(nèi)容就是這些了,完全沒有必要去看那些長(zhǎng)篇大論什么是原型的文章,初學(xué)者會(huì)越看越迷糊。 

當(dāng)然如果你想了解更多原型的深入內(nèi)容,可以閱讀我 之前寫的文章。

ES5 實(shí)現(xiàn)繼承

ES5 實(shí)現(xiàn)繼承總的來(lái)說(shuō)就兩種辦法,之前寫過這方面的內(nèi)容,就直接復(fù)制來(lái)用了。

總的來(lái)說(shuō)這部分的內(nèi)容我覺得在當(dāng)下更多的是為了應(yīng)付面試吧。

組合繼承

組合繼承是最常用的繼承方式,

function Parent(value) {
 this.val = value
}
Parent.prototype.getValue = function() {
 console.log(this.val)
}
function Child(value) {
 Parent.call(this, value)
}
Child.prototype = new Parent()

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

以上繼承的方式核心是在子類的構(gòu)造函數(shù)中通過 Parent.call(this) 繼承父類的屬性,然后改變子類的原型為 new Parent() 來(lái)繼承父類的函數(shù)。

這種繼承方式優(yōu)點(diǎn)在于構(gòu)造函數(shù)可以傳參,不會(huì)與父類引用屬性共享,可以復(fù)用父類的函數(shù),但是也存在一個(gè)缺點(diǎn)就是在繼承父類函數(shù)的時(shí)候調(diào)用了父類構(gòu)造函數(shù),導(dǎo)致子類的原型上多了不需要的父類屬性,存在內(nèi)存上的浪費(fèi)。

JS怎樣實(shí)現(xiàn)繼承

寄生組合繼承

這種繼承方式對(duì)組合繼承進(jìn)行了優(yōu)化,組合繼承缺點(diǎn)在于繼承父類函數(shù)時(shí)調(diào)用了構(gòu)造函數(shù),我們只需要優(yōu)化掉這點(diǎn)就行了。

function Parent(value) {
 this.val = value
}
Parent.prototype.getValue = function() {
 console.log(this.val)
}

function Child(value) {
 Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
 constructor: {
  value: Child,
  enumerable: false,
  writable: true,
  configurable: true
 }
})

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

以上繼承實(shí)現(xiàn)的核心就是將父類的原型賦值給了子類,并且將構(gòu)造函數(shù)設(shè)置為子類,這樣既解決了無(wú)用的父類屬性問題,還能正確的找到子類的構(gòu)造函數(shù)。

JS怎樣實(shí)現(xiàn)繼承

Babel 如何編譯 ES6 Class 的

為什么在前文說(shuō) ES5 實(shí)現(xiàn)繼承更多的是應(yīng)付面試呢,因?yàn)槲覀儸F(xiàn)在可以直接使用 class 來(lái)實(shí)現(xiàn)繼承。

但是 class 畢竟是 ES6 的東西,為了能更好地兼容瀏覽器,我們通常都會(huì)通過 Babel 去編譯 ES6 的代碼。接下來(lái)我們就來(lái)了解下通過 Babel 編譯后的代碼是怎么樣的。

function _possibleConstructorReturn (self, call) { 
  // ...
  return call && (typeof call === 'object' || typeof call === 'function') ? call : self; 
}

function _inherits (subClass, superClass) { 
  // ...
  subClass.prototype = Object.create(superClass && superClass.prototype, { 
    constructor: { 
      value: subClass, 
      enumerable: false, 
      writable: true, 
      configurable: true 
    } 
  }); 
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}


var Parent = function Parent () {
  // 驗(yàn)證是否是 Parent 構(gòu)造出來(lái)的 this
  _classCallCheck(this, Parent);
};

var Child = (function (_Parent) {
  _inherits(Child, _Parent);

  function Child () {
    _classCallCheck(this, Child);
  
    return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
  }

  return Child;
}(Parent));

以上代碼就是編譯出來(lái)的部分代碼,隱去了一些非核心代碼,我們先來(lái)閱讀 _inherits 函數(shù)。

設(shè)置子類原型部分的代碼其實(shí)和寄生組合繼承是一模一樣的,側(cè)面也說(shuō)明了這種實(shí)現(xiàn)方式是最好的。但是這部分的代碼多了一句 Object.setPrototypeOf(subClass, superClass),其實(shí)這句代碼的作用是為了繼承到父類的靜態(tài)方法,之前我們實(shí)現(xiàn)的兩種繼承方法都是沒有這個(gè)功能的。

然后 Child 構(gòu)造函數(shù)這塊的代碼也基本和之前的實(shí)現(xiàn)方式類似。所以總的來(lái)說(shuō) Babel 實(shí)現(xiàn)繼承的方式還是寄生組合繼承,無(wú)非多實(shí)現(xiàn)了一步繼承父類的靜態(tài)方法。

繼承存在的問題

講了這么些如何實(shí)現(xiàn)繼承,現(xiàn)在我們來(lái)考慮下繼承是否是一個(gè)好的選擇?

總的來(lái)說(shuō),我個(gè)人不怎么喜歡繼承,原因呢就一個(gè)個(gè)來(lái)說(shuō)。

我們先看代碼。假如說(shuō)我們現(xiàn)在要描述幾輛不同品牌的車,車必然是一個(gè)父類,然后各個(gè)品牌的車都分別是一個(gè)子類。

class Car {
  constructor (brand) {
    this.brand = brand
  }
  wheel () {
    return '4 個(gè)輪子'
  }
  drvie () {
    return '車可以開駕駛'
  }
  addOil () {
    return '車可以加油'
  }
}
Class OtherCar extends Car {}

這部分代碼在當(dāng)下看著沒啥毛病,實(shí)現(xiàn)了車的幾個(gè)基本功能,我們也可以通過子類去擴(kuò)展出各種車。

但是現(xiàn)在出現(xiàn)了新能源車,新能源車是不需要加油的。當(dāng)然除了加油這個(gè)功能不需要,其他幾個(gè)車的基本功能還是需要的。

如果新能源車直接繼承車這個(gè)父類的話,就出現(xiàn)了第一個(gè)問題 ,大猩猩與香蕉問題。這個(gè)問題的意思是我們現(xiàn)在只需要一根香蕉,但是卻得到了握著香蕉的大猩猩,大猩猩其實(shí)我們是不需要的,但是父類還是強(qiáng)塞給了子類。繼承雖然可以重寫父類的方法,但是并不能選擇需要繼承什么東西。

另外單個(gè)父類很難描述清楚所有場(chǎng)景,這就導(dǎo)致我們可能又需要新增幾個(gè)不同的父類去描述更多的場(chǎng)景。隨著不斷的擴(kuò)展,代碼勢(shì)必會(huì)存在重復(fù),這也是繼承存在的問題之一。

除了以上兩個(gè)問題,繼承還存在強(qiáng)耦合的情況,不管怎么樣子類都會(huì)和它的父類耦合在一起。

既然出現(xiàn)了強(qiáng)耦合,那么這個(gè)架構(gòu)必定是脆弱的。一旦我們的父類設(shè)計(jì)的有問題,就會(huì)對(duì)維護(hù)造成很大的影響。因?yàn)樗械淖宇惗己透割愸詈显谝黄鹆?,假如更改父類中的任何東西,都可能會(huì)導(dǎo)致需要更改所有的子類。

如何解決繼承的問題

繼承更多的是去描述一個(gè)東西是什么,描述的不好就會(huì)出現(xiàn)各種各樣的問題,那么我們是否有辦法去解決這些問題呢?答案是組合。

什么是組合呢?你可以把這個(gè)概念想成是,你擁有各種各樣的零件,可以通過這些零件去造出各種各樣的產(chǎn)品,組合更多的是去描述一個(gè)東西能干什么。

現(xiàn)在我們把之前那個(gè)車的案例通過組合的方式來(lái)實(shí)現(xiàn)。

function wheel() {
 return "4 個(gè)輪子";
}
function drvie() {
 return "車可以開駕駛";
}
function addOil() {
 return "車可以加油";
}
// 油車
const car = compose(wheel, drvie, addOil)
// 新能源車
const energyCar = compose(wheel, drive)

從上述偽代碼中想必你也發(fā)現(xiàn)了組合比繼承好的地方。無(wú)論你想描述任何東西,都可以通過幾個(gè)函數(shù)組合起來(lái)的方式去實(shí)現(xiàn)。代碼很干凈,也很利于復(fù)用。

關(guān)于JS怎樣繼承就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI