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中繼承的實作。
原型鏈繼承的基本模式,就是讓子類型的原型物件指向父類型的一個實例,然後再為其原型擴展方法。
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屬性都會是傳入的參數。如果這裡不傳參數,後邊也沒有的辦法為父類別建構子傳參了。因此很少單獨使用原型鏈繼承模式。
借用建構子可以解決引用型別屬性被分享的問題。所謂「借用」建構函數,就是在子類別建構子中呼叫父類別的建構子---別忘了函數中 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上的屬性與方法。
組合繼承借用建構子定義實例屬性,使用原型鏈共享方法。組合繼承將原型鏈模式和借用建構函式結合起來,從而發揮二者之長,彌補各自不足,是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 ,覆寫了原型中的兩個同名屬性。
如下的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 } })
寄生式繼承很好理解,只是一個封裝了繼承過程的工廠函數。由於方法直接定義在物件上,寄生式繼承新增的方法不能重複使用。
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
前面提到組合繼承是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中文網其他相關文章!