前言
繼承是JavaScript面向對象編程非常重要的一個特性,基本在日常使用以及面試過程中都會使用到。查閱一些資料後整理以下幾種繼承的方法記錄在自己的博客中。
如何實現繼承
原型鏈繼承
原型鏈繼承是最簡單的一種繼承方式,只需要將子類的
prototype
值等於父類的實例即可
function Animal(){
this.superType = 'Animal'
}
Animal.prototype.getSuperType = function(){
console.log(this.superType);
}
function Cat(name){
this.name = name;
this.type = 'Cat';
}
// 原型繼承
Cat.prototype = new Animal();
var cat = new Cat()
cat.getSuperType(); // 控制檯輸出Animal
以上代碼是將Animal
的實例覆蓋Cat
的原型,本質是重寫原型對象,代之一個新類型的實例。使Cat
擁有Animal
的實例的所有屬性和方法(getSuperType
爲原型方法),並且還有個指針指向了Animal
的原型。當創建Cat
的實例cat
時,cat
指向的是Cat
的原型,Cat
的原型又指向Animal
的原型。
缺點
- 引用類型值的原型屬性會被共享
- 在創建子類型的實例時,無法向超類型的構造函數傳遞參數
構造函數繼承
借用構造函數繼承也是非常簡單地一種繼承方式,即在子類構造函數的內部調用父類型構造函數。代碼實現爲:
function Animal(name){
this.name = name;
}
Animal.prototype.getName = function(){
console.log(this.name);
}
function Cat(){
Animal.call(this, "cat");
this.eat = "fish"
}
var instance = new Cat();
console.log(instance.name);
console.log(instance.eat); // 控制檯相繼輸出 cat fish
instance.getName() // error getName undefined
以上代碼中的 Animal
只接受一個參數 name
,該參數會直接賦給一個屬性。在 Cat
構造函數內部調用 Animal
構造函數時,實際上視爲 Cat
的實例設置了 name
屬性。爲了確保 Animal
構造函數不會重寫子類的屬性,可以在調用父類構造函數後,再添加應該在子類型中定義的屬性。
優點
- 引用類型的原型屬性不會被共享
- 可以在子類型構造函數中向父類構造函數傳遞參數
缺點
- 方法都在構造函數中定義,函數無法複用
- 在父類的原型定義的方法,對子類型是不可見,導致所有的類型都只能使用構造函數
組合繼承
組合繼承,也叫做僞經典繼承,指的是將原型鏈和借用構造函數組合到一塊
function SuperType(name) {
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.getName = function() {
console.log(this.name)
}
function SubType(name, age) {
// 繼承屬性
SuperType.call(this, name)
this.age = age
}
// 繼承方法
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function () {
console.log(this.age)
}
let instance1 = new SubType("tom", "8")
instance1.colors.push("black")
console.log(instance1.colors) // red, blue, green, black
instance1.getName() // tom
instance1.sayAge() // 8
let instance2 = new SubType("jerry", "9")
console.log(instance2.colors) // red, blue, green
instance2.getName() // jerry
instance2.sayAge() // 9
此例子中, SuperType
構造函數定義了兩個屬性: name
和 colors
。 SuperType
定義了一個方法 sayName()
。 SubType
構造函數在調用 SuperType
構造函數時傳入 name
參數,緊接着又定義了它自己的屬性 age
。然後將 SuperType
的實例賦值給 SubType
的原型,然後又在該新原型上定義了方法 sayAge
。這樣就可以讓兩個不同的 SubType
實例即擁有自己的屬性,又可以使用相同的方法。
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優點,成爲 JavaScript
中最常用的繼承模式。而且,instanceof
和 isPortotypeOf()
也能夠用於識別組合繼承創建的對象。
缺點
- 會調用兩次超類構造函數,並且會分別在實例和原型上有重複的屬性
原型式繼承
原型式繼承:其思想是藉助原型,可以基於已有的對象創建新的對象,同時還不用創建自定義類。以下代碼,在
object()
函數內部,先創建了一個臨時性的構造函數,然後將傳入的對象作爲這個構造函數原型,最後返回了這個臨時類型的一個新實例。從本質上講,object()
對傳入其中的對象執行了一次淺複製。
function object(o) {
function F(){}
F.prototype = o
return new F()
}
var person = {
name: 'Tom',
friends: ['Jerry', 'Sherry', 'Harry']
}
var anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')
var yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Bob')
console.log(person.friends) // Jerry, Sherry, Harry, Rob, Bob
還可以使用 ES5
新增的 Object.create()
方法進行創建
var anotherPerson = Object.create(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')
var yetAnotherPerson = Object.create(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Bob')
console.log(person.friends) // Jerry, Sherry, Harry, Rob, Bob
如果只是想讓一個對象與另一個對象保持類似的情況下,原型式繼承是可以完全勝任的
寄生式繼承
寄生式繼承的思路與構造函數和工廠模式類似,即創建一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最後再返回對象
function createAnother(original) {
var clone = Object.create(original)
clone.sayHi = function () {
console.log('Hi')
}
return clone
}
上述代碼中, createAnother()
函數接收一個參數,也就是將要作爲新對象基礎的對象。然後,把 original
傳遞給 object
函數,將返回的結果賦值給clone。再爲 clone
對象添加一個新方法 sayHi()
,最後返回 clone
對象。
var person = {
name: 'Tom',
friends: ['Jerry', 'Sherry', 'Harry']
}
var anotherPerson = createAnother(person)
anotherPerson.sayHi() // Hi
在主要考慮對象而不是自定義類型和構造函數的情況下,寄生式繼承也是一種有用的模式。
寄生組合式繼承
寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。其背後的基本思路是:不必爲指定子類行的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然後再將結果指定給子類型的原型。代碼如下:
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
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)
}
該代碼只調用了一次 SuperType
構造函數,並且因此避免了在 SubType.prototype
上創建不必要的,多餘的屬性。與此同時,原型鏈還能保持不變。屬於最理想的繼承範式。
總結
繼承是 JS
中極爲重要的一塊知識,目前只能參考資料將這些記錄下來,完全喫透還需要慢慢實戰。