JS: OOP - Inheritance
接續上一篇的物件導向介紹的OOP基本介紹,這篇說明Object.create、contstructor function和ES6 class語法如何建立prototypes(原型)之間的繼承關係,形成所謂的原型鏈(prototype chain)
Object.create
複習一下,Object.create方法暗地裡其實就是用來鏈結某東西與prototype之間的關係,例如前一篇是用 const jeck = Object.create(UserProto)
,讓物件jeck可以繼承UserProto的prototype並存取其方法。因此,同樣也能透過Object.create建立prototypes之間的繼承關係。
延續前一篇假設已經有一個父類別的prototype為 UserProto
:
const AdminProto = Object.create(UserProto);
AdminProto.init = function(account, password, key){
UserProto.init.call(this, account, password);
this.key = key;
}
const mike = Object.create(AdminProto);
範例中使用Object.create建立prototypes之間的繼承關係,AdminProto的父層prototype即為UserProto,這點可以用 mike.__proto__.__proto__ === UserProto
來檢查。
此外,為了繼承(委託)父層prototype已有的 init(account, password)
函式,範例中對子層prototype,也就是AdminProto建立了一個同名但部分參數不同的函式 init(account, password, key)
,並在函式裡用 call
方法去綁定父層prototype的init函式。
到這裡可能會想說為何不可以直接呼叫 UserProto.init
函式?原因在於如果用一般的函式呼叫方法,按照this的規則,一般函式的this是undefined,也就不能讓init函式做到:
this.account = account;
this.password = password;
因為this是undefined。
子層prototype也可以擁有自己的方法,這點的寫法就跟父層prototype寫函式的方式一樣,就不贅述了。
Constructor Function
要如何讓constructor functions們的prototypes之間有繼承關係?
看到這個問題可以回想剛才Object.create案例也有類似的狀況。前面案例用Object.create就可以讓UserProto和AdminProto的prototypes之間有繼承關係,所以同樣的手法也可用在constructor functions的prototypes們身上。
先來建立子層物件的constructor function,寫法類似於Object.create案例裡AdminProto的init函式:
const Admin = function(account, password, key){
User.prototype.call(this, account, password);
this.key = key;
}
Admin.prototype = Object.create(User.prototype);
conse mike = new Admin("mike456", 456, a007)
再複述一次,Object.create是用來建立prototypes之間的親子繼承關係,所以沒辦法直接寫成:
Admin.prototype = User.prototype
這句的意思是指Admin.prototype其實就等於User.prototype,兩個指向相同的prototype,並沒有建立兩個prototypes之間的繼承關係。
如果想檢查Admin這個constructor function的prototype是否指向正確的prototype,除了可以用
mike.__proto__
來檢查物件的__proto__
是否指向Admin的prototype,或者Admin.prototype.isPrototypeOf(mike)
直接確認Admin的prototype是否就是其物件mike的prototype,
也可以用 Admin.prototype.constructor
來看constructor是否指回Admin自己。
ES6 Class
相較於前面兩個比較複雜的繼承關係建立方式,ES6 Class的繼承就顯得比較親切許多(如果有寫過其他程式語言的話)
class Admin extends User {
constructor(account, password, key){
super(account, password);
this.key = key;
}
// 可以寫Admin自己的prototype方法
}
ES6 class語法是很直覺地直接用 extends
關鍵字讓Admin繼承User,而constructor裡的 super
就是向JavaScript明示要呼叫父層User的constructor。
此外,如果Admin的constructor沒有要輸入account和password以外的屬性(key),其實也可以不必特別用 super()
,因為 extends
這個關鍵字就會在背地裡自動呼叫 super(account, password)
,就能省去這道功夫。
父層和子層或者他們各自的prototypes都可以擁有自己的屬性,如果想要檢查是否為某個constructor的某個屬性或方法,可以用 instance.hasOwnProperty(/* property name */)
來確認。
另外,有關於物件導向還有幾個比較分散的小主題,像是靜態屬性、靜態方法、setter和getter,就統一彙整在最後一篇文章。
References The Complete JavaScript Course 2023: From Zero to Expert!