Class - rozszerzanie typów
W poprzednich rozdziałach poznaliśmy konstruktory i klasy. Tworzone za ich pomocą obiekty dziedziczyły funkcjonalności ze swoich prototypów, a ich prototypy dziedziczyły ze swoich. Taka drabinka dziedziczenia tworzona była dla nas w zasadzie automatycznie.
My jako programiści możemy też sprawić, by dany typ dziedziczył z innego typu odpowiednio go rozszerzając.
Rozszerzanie typów
Aby rozszerzyć dany typ obiektów, skorzystamy z instrukcji extends:
class Enemy { //to może być też konstruktor
constructor(x, y) {
this.x = x;
this.y = y;
this.type = "normalny";
}
fly() {
return this.type + " - lecę";
}
}
class EnemyShooter extends Enemy {
constructor(x, y) {
super(x, y);
this.type = "strzelający";
}
shoot() {
return this.type + " - strzelam";
}
}
const enemyNormal = new Enemy(10, 20);
console.log(enemyNormal.fly()); //"normalny - lecę"
console.log(enemyNormal.shoot()); //błąd - nie ma takiej metody
const enemyShooter = new EnemyShooter(20, 30);
console.log(enemyShooter.fly()); //"strzelający - lecę"
console.log(enemyShooter.shoot()); //"strzelający - strzelam"
W konstruktorze klasy rozszerzającej pojawiła nam się instrukcja super()
, która w przypadku konstruktora służy do wywołania kodu z konstruktora klasy którą rozszerzamy. Jeżeli wymagał on jakiś wartości, powinniśmy je przekazać w nawiasach super()
.
class Point {
constructor(x, y) {
}
}
class Dot extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
}
W przypadku rozszerzania innych metod, także skorzystamy z instrukcji super()
, po której podajemy nazwę metody, którą chcemy rozszerzyć:
class Enemy {
constructor(x, y) {
this.x = x;
this.y = y;
this.type = "normalny";
}
fly() {
return this.type + " - lecę";
}
}
class EnemyShooter extends Enemy {
constructor(x, y) {
super(x, y);
this.type = "strzelający";
}
shoot() {
return this.name + " - strzelam";
}
fly() {
const text = super.fly();
return text + " i czasami strzelam";
}
}
const enemyNormal = new Enemy(10, 20);
enemyNormal.fly(); //"normalny - lecę"
const enemyShooter = new EnemyShooter(20, 30);
console.log(enemyShooter.fly()); //"strzelający - lecę i czasami strzelam"
I tutaj ponownie - gdyby oryginalna funkcja fly()
wymagała parametrów, przydało by się je podać, inaczej rozszerzana funkcja odpalona była by z wartościami undefined.
class Enemy {
flyTo(x, y) {
return `Lecę do pozycji ${x} x ${y}`
}
}
class EnemyShooter extends Enemy {
flyTo(x, y) {
const text = super.flyTo(); //wywołaliśmy powyższą funkcję bez wartości
return text + ' i strzelam';
}
}
const enemyNormal = new Enemy(10, 20);
enemyNormal.flyTo(100, 200); //"Lecę do pozycji 100 x 200"
const enemyShooter = new EnemyShooter(20, 30);
console.log(enemyShooter.flyTo(100, 200)); //"Lecę do pozycji undefined x undefined i strzelam"
Jeżeli chcemy daną metodę przesłonić (czyli by nic nie dziedziczyła z klasy rozszerzanej) zwyczajnie nie korzystamy z instrukcji super()
:
class Enemy {
flyTo(x, y) {
return `Lecę do pozycji ${x} x ${y}`
}
}
class enemyStatic extends Enemy {
flyTo() {
//nie używamy super.flyTo(x, y);
return "Ten typ obiektów w ogóle nie lata";
}
}
A jeżeli chcemy by dana metoda (w tym konstruktor), została w niezmienionej postaci względem klasy, którą właśnie rozszerzamy, po prostu pomijamy jej kod:
class Shape {
constructor(x, y, side) { ... }
area() { ... }
calculate() { ... }
radius() { ... }
}
class Triangle extends Shape {
constructor(x, y, side) {
super(x, y, side);
this.type = "triangle";
}
area() { ... }
//metody calculate() i radius() będą brane z klasy Shape
}
Podobne do powyższych operacji możemy też wykonać za pomocą konstruktorów. Niestety w przypadku tamtej składni kod jest bardziej skomplikowany, a i mamy mniejsze możliwości działania, ponieważ nie możemy swobodnie rozszerzać wbudowanych typów. W dzisiejszych czasach do rozszerzania typów polecam pozostać przy klasach. Jeżeli chcesz zobaczyć, jak to wyglądało, spójrz na ten przykład.
Rozszerzanie wbudowanych typów
Tworzenie własnych typów danych na bazie wbudowanych w starszych wersjach Javascript nie było trywialną sprawą, co pokazują liczne wątki i artykuły. Na szczęście zmieniło się to wraz z wprowadzeniem klas:
class MyArray extends Array {
constructor(...param) {
super(...param);
}
sortNr() {
return this.sort((a, b) => a - b);
}
}
const tab1 = new Array(4, 5, 20, 21, 2.1, 1.2, 3);
tab1.sortNr(); //błąd : nie ma takiej metody
const tab2 = new MyArray(4, 5, 20, 21, 2.1, 1.2, 3);
tab2.sortNr();
console.log(tab2); //[1.2, 2.1, 3, 4, 5, 20, 21]
class MyString extends String {
constructor(...param) {
super(...param);
}
mix() {
return [...this].map((letter, i) => (i % 2 === 0) ? letter.toUpperCase() : letter.toLowerCase()).join("");
}
}
const txt1 = new String("lubie koty i psy");
txt1.mix(); //błąd : nie ma takiej metody
const txt = new MyString("lubie koty i psy");
console.log(txt.mix()); //LuBiE KoTy i pSy
Trening czyni mistrza
Jeżeli chcesz sobie potrenować zdobytą wiedzę, zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania