雖然js只有prottype,但如果想實現OOP裡的class間的繼承,其實也發展出相對應的作法:透過手動連結兩個建構函式的prototype chain,就可以實現一個constructor繼承另一個constructor。這邊說的手動,就是使用Object.create()。
先從一個Person建構函式開始,裡面寫了未來使用new來新建instance會建立的屬性,並在Person的prototype屬性建立了方法,如此一來,instance可以透過prototype chain取用這個方法,做到看起來像是繼承的prototypal inheritance。
const Person = function (firstName, birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
};
Person.prototype.calcAge = function () {
return 2023 - this.birthYear;
};
接著我們建了另一個建構函式Student,想實現Student為Person的child class,也就是從Person那繼承原本就有的屬性與方法,另外又有自己獨特的屬性及方法(可依需求而變)。
在屬性的部分,我們製造兩者連結的方法是在Student呼叫Person,所以Person的屬性若有變更,Student的部份就會隨之而變。而在這邊使用call()的原因是,其實呼叫Person就是呼叫一個一般函式,所以Person裡的this
指向的是undefined
,故使用call()來手動設定this指向未來新增instance的空白物件(與this.major的this是同一個東西)。同時也可以視情況為child class新增屬性,這邊增加了主修科目。
const Student = function (firstName, birthYear, major) {
Person.call(this, firstName, birthYear);
this.major = major;
};
在方法的部分,則是使用Object.create()將Person.prototype裡的方法寫進Student.prototype,讓Student的instance能夠繼承。...才怪!!因為我們現在要做的是Student繼承Person啊,所以當然是寫在Student的[[prototype]]裡啊...才怪!!!
其實應該是要寫進Student.prototype的[[prototype]]才對啦!!
事情是這樣的,因為我們希望Student的instance也能使用Person的instance能用的方法,將Person.prototype寫進Student.prototype [[prototype]]才能建立正確的prototype chain。下圖要從紅房子往上看,也就是prototype chain的查找方向。
Student.prototype = Object.create(Person.prototype);
看一下以new建立Student的instance的結果,於是就達成class之間的繼承了。
const cindy = new Student("Cindy", 1996, "Finance");
console.log(cindy);
console.log(cindy.calcAge());
使用ES6 class來做OOP class的繼承,一切輕鬆簡單,如果沒有要增加新的屬性或方法,一行就做完了
class PersonCl {
constructor(firstName, birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
}
calcAge() {
return 2023 - this.birthYear;
}
}
class StudentCl extends PersonCl {}
若要新增屬性,在constructor裡呼叫super(),然後在寫要多加的屬性。
class StudentCl extends PersonCl {
constructor(firstName, birthYear, major) {
super(firstName, birthYear);
this.major = major;
}
}
如果要新增或修改繼承來的方法,則直接在StudentCl裡加多方法即可。需要注意的是,這個新增或修改,其實是寫進子類別的[[prototype]],依prototype chain找的時候,會先找到自己的[[prototype]],而非修改了父類別那邊的方法。
class StudentCl extends PersonCl {
constructor(firstName, birthYear, major) {
super(firstName, birthYear);
this.major = major;
}
greet() {
return `I'm ${this.firstName} and my major is ${this.major}`;
}
}
使用Object.create()來實現class間的繼承,所用的想法是把需要被繼承的東西直接寫進對方的[[prototype]]裡。像是想建立一個Person的instance,就直接把建立屬性的方法及其他方法寫進instance的[[prototype]],像是例子中的john。
const PersonProto = {
createProperty(firstName, birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
},
calcAge() {
return 2023 - this.birthYear;
},
};
const john = Object.create(PersonProto);
john.createProperty("John", 1995);
下圖是從上面抓下來改寫的,但其實概念也都是一樣的,從紅房子開始往上看,就可以建立prototype chain,也就知道程式該怎麼寫
把PersonProto先寫進StudentProto的[[prototype]],再把StudentProto寫進instance from Student的[[prototype]]
const StudentProto = Object.create(PersonProto);
const cindy = Object.create(StudentProto);
如果只單純繼承屬性和方法而無新增或修改,其實這樣就能作用了
cindy.createProperty("Cindy", 2000);
console.log(cindy);
但一般而言還是會客製化一些子類別特有的屬性和方法,則是在instance的[prototype]新增方法,如此一來,當follow prototype chain查找時,會先找到第一層也就是有新增屬性的createProperty(),其效果就像是覆蓋
了從父類別那邊來的方法。
StudentProto.createProperty = function (firstName, birthYear, major) {
PersonProto.createProperty.call(this, firstName, birthYear);
this.major = major;
};
const cindy = Object.create(StudentProto);
cindy.createProperty("Cindy", 2000, "Finance");
console.log(cindy);
三種方法各有不同特性,constructor的方法讓人體會prototype的運作機制;ES6 class的結構真的友善很多,好寫也好讀;而Object.create()提供了更靈活的使用方法。
udemy-The Complete Javascript Course