首頁  >  文章  >  web前端  >  js中有哪些繼承方式?

js中有哪些繼承方式?

PHP中文网
PHP中文网原創
2017-06-21 09:57:371104瀏覽

1 ES6中的繼承

ES6使用class關鍵字定義類,使用extends關鍵字繼承類別。子類別的constructor建構方法中必須呼叫super方法來獲得父類別的」this「對象,呼叫super時可以向父建構子傳參。子類別可以透過super物件直接使用父類別的屬性和方法,也可以透過同名屬性或方法覆寫父類別中的定義。

class Father {
  constructor () {
    this.surname = '王'
    this.money = Infinity
  }
  sayName () {
    console.log(`My surname is ${this.surname}.`)
  }
}

class Son extends Father {
  constructor (firstname) {
    super()
    this.firstname = firstname
  }
  sayName () {
    console.log(`My name is ${super.surname}${this.firstname}.`)
  }
  sayMoney () {
    console.log(`I have ${this.money} money.`)
  }
}

let Sephirex = new Son('撕葱')
Sephirex.sayName()
Sephirex.sayMoney()

ES6中的類別和繼承本質上是使用prototype實作的語法糖,類別中定義的方法相當於在prototype上定義方法,constructor方法中定義屬性相當於建構子模式,super方法相當於在子類別中呼叫父類別的建構子。以下繼續討論ES5中繼承的實作。

2 原型鏈繼承

原型鏈繼承的基本模式,就是讓子類型的原型物件指向父類型的一個實例,然後再為其原型擴展方法。

function Person (name) {
  this.name = name
  this.likes = ['apple', 'orange']
}
Person.prototype.sayName = function () {
  console.log(this.name)
}

function Worker () {
  this.job = 'worker'
}
Worker.prototype = new Person()
Worker.prototype.sayJob = function () {
  console.log(this.job)
}

let Tom = new Worker()
let Jerry = new Worker()
Tom.likes.push('grape')
console.log(Jerry.likes) // [ 'apple', 'orange', 'purple' ]

原理:之前的文章我們討論了__proto__和prototype。子類別的實例中有一個__proto__指針,指向其建構函數的原型物件。而子類別建構子的原型指向父類別的一個實例,父類別實例中的__proto__又指向了父類別建構子的原型......如此層層遞進,就構成了原型鏈。
要注意的是,即使父類別中引用類型的屬性是在建構函式中定義的,還是會被子類別實例共用。這是因為子類別建構子的原型其實是父類別的一個實例,於是父類別的實例屬性自然就變成子類別的原型屬性,而引用類型值的原型屬性會在實例之間共用。
原型鏈的另一個問題是,沒有辦法在不影響所有物件實例的情況下,給父類別的建構子傳遞參數。像上面的例子,用 Worker.prototype = new Person() 將子類別原型指向父類別實例的時候, 如果傳入了初始化參數,則所有子類別的實例name屬性都會是傳入的參數。如果這裡不傳參數,後邊也沒有的辦法為父類別建構子傳參了。因此很少單獨使用原型鏈繼承模式。

3 借用建構子

借用建構子可以解決引用型別屬性被分享的問題。所謂「借用」建構函數,就是在子類別建構子中呼叫父類別的建構子---別忘了函數中 this 的指向跟函數在哪裡定義無關,而只跟在哪裡呼叫有關。我們可以利用call或apply,在子類別實例上呼叫父類別的建構函數,以取得父類別的屬性和方法,類似ES6子類別建構子中呼叫super方法。

function Person (name) {
  this.name = name
  this.likes = ['apple', 'orange']
}

function Worker (name) {
  Person.call(this, name)
  this.job = 'worker'
}

let Tom = new Worker('Tom')
Tom.likes.push("grape")

let Jerry = new Worker('Jerry')

console.log(Tom.likes) // [ 'apple', 'orange', 'grape' ]
console.log(Jerry.likes) // [ 'apple', 'orange' ]

單純使用建構子的問題在於函數無法重複使用,且子類別無法取得父類別prototype上的屬性與方法。

4 組合繼承

組合繼承借用建構子定義實例屬性,使用原型鏈共享方法。組合繼承將原型鏈模式和借用建構函式結合起來,從而發揮二者之長,彌補各自不足,是js中最常用的繼承模式。

function Person (name) {
  this.name = name
  this.likes = ['apple', 'orange']
}
Person.prototype.sayName = function () {
  console.log(this.name)
}

function Worker (name, job) {
  Person.call(this, name) // 第二次调用 Person()
  this.job = job
}
Worker.prototype = new Person() // 第一次调用 Person()
Worker.prototype.constructor = Worker
Worker.prototype.sayJob = function () {
  console.log(this.job)
}

let Tom = new Worker('Tom', 'electrician')
Tom.likes.push('grape')
console.log(Tom.likes) // [ 'apple', 'orange', 'grape' ]
Tom.sayName() // Tom
Tom.sayJob() // electrician

let Jerry = new Worker('Jerry', 'woodworker')
console.log(Jerry.likes) // [ 'apple', 'orange' ]
Jerry.sayName() // Jerry
Jerry.sayJob() // woodworker

組合繼承也並非沒有缺點,那就是繼承過程會兩次呼叫父類別建構子。在第一次呼叫Person 建構函式時,Worker.prototype 會得到兩個屬性:name 和likes;當呼叫Worker 建構函式時,又會呼叫一次Person 建構函數,這次直接建立了實例屬性name 和likes ,覆寫了原型中的兩個同名屬性。

5 原型式繼承

如下的object函數是道格拉斯·克羅克福德在一篇文章中記錄的。在 object函數內部,先創建了一個臨時性的構造函數,然後將傳入的對像作為這個構造函數的原型,最後返回了這個臨時類型的一個新實例。從本質上講, object() 對傳入其中的物件執行了一次淺複製。這種繼承方式,相當於把父類型的屬性和方法複製一份給子類型,然後再為子類型加入各自的屬性和方法。
這種方式同樣會共享引用類型值的屬性。

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

let Superhero = {
  name: 'Avenger',
  skills: [],
  sayName: function () {
    console.log(this.name)
  }
}

let IronMan = object(Superhero)
IronMan.name = 'Tony Stark'
IronMan.skills.push('fly')

let CaptainAmerica = object(Superhero)
CaptainAmerica.name = 'Steve Rogers'
CaptainAmerica.skills.push('shield')

IronMan.sayName() // Tony Stark
console.log(IronMan.skills) // [ 'fly', 'shield' ]

ES5中以 Object.create() 方法規範化了原型式繼承。這個方法接收兩個參數:一個用作新物件原型的物件和(可選的)一個為新物件定義額外屬性的物件。在傳入一個參數的情況下,Object.create() 與 object() 方法的行為相同。 Object.create() 方法的第二個參數與 Object.defineProperties() 方法的第二個參數格式相同。

let CaptainAmerica = Object.create(Superhero, {
  name: {
    value: 'Steve Rogers',
    configurable: false
  }
})

6 寄生式繼承

寄生式繼承很好理解,只是一個封裝了繼承過程的工廠函數。由於方法直接定義在物件上,寄生式繼承新增的方法不能重複使用。

function inherit(parent){
  var clone = Object.create(parent)
  clone.name = 'hulk'
  clone.sayHi = function(){
    console.log("hi")
  }
  return clone
}

let Hulk = inherit(Superhero)

Hulk.sayName() // hulk
Hulk.sayHi() // hi

7 寄生組合式繼承

前面提到組合繼承是js中最常用的繼承方式,但不足是會呼叫兩次父類別的建構子。寄生組合式繼承可以解決這個問題,並且被認為是包含引用類型值的物件最理想的繼承方式。
寄生組合式繼承的基本想法是,不必為了指定子類別的原型而呼叫父類別的建構函數,需要的只是父類別原型的一個副本而已。寄生組合式繼承就是藉用建構函式來繼承屬性,然後使用寄生式繼承來繼承父類別的原型。

function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype)
  prototype.constructor = subType
  subType.prototype = prototype
}

function Person (name) {
  this.name = name
  this.likes = ['apple', 'orange']
}
Person.prototype.sayName = function () {
  console.log(this.name)
}

function Worker (name, job) {
  Person.call(this, name)
  this.job = job
}
inheritPrototype(Worker, Person)

Worker.prototype.sayJob = function () {
  console.log(this.job)
}

以上是js中有哪些繼承方式?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn