Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Prototypy i dziedziczenie prototypowe

W Parku Jurajskim efektywne zarządzanie różnymi gatunkami dinozaurów wymaga zrozumienia podobieństw między nimi i możliwości dziedziczenia cech. JavaScript używa prototypów do implementacji mechanizmu dziedziczenia - jest to jeden z najbardziej unikalnych i potężnych aspektów tego języka. Zrozumienie prototypów i dziedziczenia prototypowego jest kluczowe dla pisania wydajnego, elastycznego kodu JavaScript.

Czym są prototypy?

W JavaScript każdy obiekt ma wewnętrzną właściwość zwaną prototypem, która jest referencją do innego obiektu. Gdy próbujemy uzyskać dostęp do właściwości obiektu, JavaScript najpierw sprawdza, czy sam obiekt posiada taką właściwość. Jeśli nie, sprawdza prototyp obiektu, następnie prototyp prototypu, itd., tworząc tzw. łańcuch prototypów.

1// Tworzymy prosty obiekt
2const dinosaur = {
3  species: "Tyrannosaurus",
4  makesSound: true,
5  roar() {
6    return "ROOOOAR!";
7  }
8};
9
10// Tworzymy nowy obiekt, który dziedziczy po obiekcie dinosaur
11const rex = Object.create(dinosaur);
12rex.name = "Rex";
13rex.age = 8;
14
15// rex ma dostęp do swoich własnych właściwości
16console.log(rex.name); // "Rex"
17console.log(rex.age); // 8
18
19// rex ma również dostęp do właściwości i metod dinosaur
20console.log(rex.species); // "Tyrannosaurus"
21console.log(rex.makesSound); // true
22console.log(rex.roar()); // "ROOOOAR!"
23
24// Sprawdźmy, czy rex ma własną właściwość 'species' (nie ma)
25console.log(rex.hasOwnProperty('species')); // false
26
27// Ale ma własną właściwość 'name'
28console.log(rex.hasOwnProperty('name')); // true

W powyższym przykładzie

rex
nie ma własnej właściwości
species
, ale dostęp do
rex.species
działa, ponieważ JavaScript szuka tej właściwości w prototypie
rex
, którym jest obiekt
dinosaur
.

Łańcuch prototypów

Prototypy tworzą łańcuch, który JavaScript przeszukuje, gdy próbujemy uzyskać dostęp do właściwości. Ten mechanizm nazywamy łańcuchem prototypów (prototype chain).

1// Podstawowy obiekt przodek
2const animal = {
3  isAlive: true,
4  eat() {
5    return `${this.name} je.`;
6  }
7};
8
9// Obiekt dinozaur dziedziczący po animal
10const dinosaur = Object.create(animal);
11dinosaur.isExtinct = false;
12dinosaur.roar = function() {
13  return `${this.name} ryczy!`;
14};
15
16// Konkretny gatunek dinozaura dziedziczący po dinosaur
17const tyrannosaurus = Object.create(dinosaur);
18tyrannosaurus.name = "Tyrannosaurus";
19tyrannosaurus.diet = "carnivore";
20tyrannosaurus.length = 12; // metry
21
22// tyrannosaurus ma dostęp do właściwości wszystkich prototypów w łańcuchu
23console.log(tyrannosaurus.name); // "Tyrannosaurus" (własna właściwość)
24console.log(tyrannosaurus.isExtinct); // false (z dinosaur)
25console.log(tyrannosaurus.isAlive); // true (z animal)
26console.log(tyrannosaurus.eat()); // "Tyrannosaurus je." (metoda z animal, this odwołuje się do tyrannosaurus)
27console.log(tyrannosaurus.roar()); // "Tyrannosaurus ryczy!" (metoda z dinosaur)

Konstruktory i prototypy

W praktyce, prototypy są najczęściej używane z konstruktorami i klasami do implementowania dziedziczenia w JavaScript.

Funkcje konstruktora i prototypy

Każda funkcja konstruktora ma właściwość

prototype
, która jest używana jako prototyp dla obiektów tworzonych za pomocą tej funkcji z operatorem
new
.

1// Funkcja konstruktora dla Dinosaur
2function Dinosaur(name, species, diet) {
3  this.name = name;
4  this.species = species;
5  this.diet = diet;
6  this.isExtinct = false;
7}
8
9// Dodajemy metody do prototypu Dinosaur
10Dinosaur.prototype.roar = function() {
11  const volume = this.diet === "carnivore" ? "głośno" : "cicho";
12  return `${this.name} ryczy ${volume}!`;
13};
14
15Dinosaur.prototype.describe = function() {
16  return `${this.name} to ${this.species}, który jest ${this.diet === "carnivore" ? "mięsożerny" : "roślinożerny"}.`;
17};
18
19// Tworzymy instancje
20const trex = new Dinosaur("Rex", "Tyrannosaurus", "carnivore");
21const tricera = new Dinosaur("Trixie", "Triceratops", "herbivore");
22
23console.log(trex.roar()); // "Rex ryczy głośno!"
24console.log(tricera.roar()); // "Trixie ryczy cicho!"
25console.log(trex.describe()); // "Rex to Tyrannosaurus, który jest mięsożerny."
26
27// Wszystkie instancje współdzielą te same metody prototypu
28console.log(trex.roar === tricera.roar); // true

W tym przykładzie, metody

roar
i
describe
są definiowane na prototypie funkcji konstruktora
Dinosaur
. Wszystkie instancje utworzone za pomocą
new Dinosaur()
współdzielą te same metody, co jest znacznie bardziej efektywne niż definiowanie tych metod w konstruktorze dla każdej instancji osobno.

Sprawdzanie prototypu obiektu

Możemy sprawdzić prototyp obiektu za pomocą kilku metod:

1// Używając Object.getPrototypeOf()
2console.log(Object.getPrototypeOf(trex) === Dinosaur.prototype); // true
3
4// Używając __proto__ (właściwość przestarzała, nie zalecana w kodzie produkcyjnym)
5console.log(trex.__proto__ === Dinosaur.prototype); // true
6
7// Sprawdzanie, czy obiekt jest instancją konstruktora
8console.log(trex instanceof Dinosaur); // true
9console.log(trex instanceof Object); // true (wszystkie obiekty dziedziczą po Object.prototype)

Dziedziczenie między konstruktorami

Możemy implementować dziedziczenie między funkcjami konstruktora, łącząc ich prototypy:

1// Funkcja konstruktora dla Animal (klasa bazowa)
2function Animal(name) {
3  this.name = name;
4  this.isAlive = true;
5}
6
7Animal.prototype.eat = function(food) {
8  return `${this.name} je ${food}.`;
9};
10
11Animal.prototype.sleep = function() {
12  return `${this.name} śpi.`;
13};
14
15// Funkcja konstruktora dla Dinosaur (klasa pochodna)
16function Dinosaur(name, species, diet) {
17  // Wywołujemy konstruktor klasy bazowej
18  Animal.call(this, name);
19
20  // Dodajemy właściwości specyficzne dla Dinosaur
21  this.species = species;
22  this.diet = diet;
23  this.isExtinct = false;
24}
25
26// Ustawiamy prototyp Dinosaur, aby dziedziczył po Animal.prototype
27Dinosaur.prototype = Object.create(Animal.prototype);
28
29// Przywracamy właściwą właściwość konstruktora
30Dinosaur.prototype.constructor = Dinosaur;
31
32// Dodajemy metody specyficzne dla Dinosaur
33Dinosaur.prototype.roar = function() {
34  const volume = this.diet === "carnivore" ? "głośno" : "cicho";
35  return `${this.name} ryczy ${volume}!`;
36};
37
38// Tworzymy instancję Dinosaur
39const velociraptor = new Dinosaur("Blue", "Velociraptor", "carnivore");
40
41// Mamy dostęp do metod z Animal
42console.log(velociraptor.eat("mięso")); // "Blue je mięso."
43console.log(velociraptor.sleep()); // "Blue śpi."
44
45// Oraz do metod specyficznych dla Dinosaur
46console.log(velociraptor.roar()); // "Blue ryczy głośno!"
47
48// Sprawdzamy łańcuch prototypów
49console.log(velociraptor instanceof Dinosaur); // true
50console.log(velociraptor instanceof Animal); // true
51console.log(velociraptor instanceof Object); // true

Ten wzorzec implementacji dziedziczenia między konstruktorami jest często nazywany "dziedziczeniem pseudoklasycznym", ponieważ emuluje dziedziczenie klasowe w języku, który oryginalnie bazuje na prototypach.

Dziedziczenie wielopoziomowe

Możemy tworzyć wielopoziomowe hierarchie dziedziczenia, łącząc łańcuchy prototypów:

1// Klasa bazowa Animal
2function Animal(name) {
3  this.name = name;
4  this.isAlive = true;
5}
6
7Animal.prototype.breathe = function() {
8  return `${this.name} oddycha.`;
9};
10
11// Klasa Dinosaur dziedzicząca po Animal
12function Dinosaur(name, period) {
13  Animal.call(this, name);
14  this.period = period;
15  this.extinct = true;
16}
17
18Dinosaur.prototype = Object.create(Animal.prototype);
19Dinosaur.prototype.constructor = Dinosaur;
20
21Dinosaur.prototype.roar = function() {
22  return `${this.name} ryczy!`;
23};
24
25// Klasa Therapod dziedzicząca po Dinosaur
26function Therapod(name, period, speed) {
27  Dinosaur.call(this, name, period);
28  this.speed = speed; // km/h
29  this.bipedal = true;
30}
31
32Therapod.prototype = Object.create(Dinosaur.prototype);
33Therapod.prototype.constructor = Therapod;
34
35Therapod.prototype.run = function() {
36  return `${this.name} biegnie z prędkością ${this.speed} km/h!`;
37};
38
39// Klasa Tyrannosaurus dziedzicząca po Therapod
40function Tyrannosaurus(name, size) {
41  Therapod.call(this, name, "Late Cretaceous", 30);
42  this.size = size;
43  this.teeth = 60;
44}
45
46Tyrannosaurus.prototype = Object.create(Therapod.prototype);
47Tyrannosaurus.prototype.constructor = Tyrannosaurus;
48
49Tyrannosaurus.prototype.bite = function() {
50  return `${this.name} gryzie z ogromną siłą dzięki ${this.teeth} zębom!`;
51};
52
53// Tworzymy instancję Tyrannosaurus
54const rex = new Tyrannosaurus("Rex", "bardzo duży");
55
56// Mamy dostęp do metod wszystkich prototypów w łańcuchu
57console.log(rex.breathe()); // "Rex oddycha." (z Animal)
58console.log(rex.roar()); // "Rex ryczy!" (z Dinosaur)
59console.log(rex.run()); // "Rex biegnie z prędkością 30 km/h!" (z Therapod)
60console.log(rex.bite()); // "Rex gryzie z ogromną siłą dzięki 60 zębom!" (z Tyrannosaurus)
61
62// Sprawdzamy łańcuch prototypów
63console.log(rex instanceof Tyrannosaurus); // true
64console.log(rex instanceof Therapod); // true
65console.log(rex instanceof Dinosaur); // true
66console.log(rex instanceof Animal); // true
67console.log(rex instanceof Object); // true

Prototypy a klasy w ES6+

ES6 wprowadził składnię klas, która pod spodem używa prototypów, ale oferuje bardziej przyjazną, podobną do klasycznej obiektowości składnię:

1// Klasa bazowa Animal
2class Animal {
3  constructor(name) {
4    this.name = name;
5    this.isAlive = true;
6  }
7
8  eat(food) {
9    return `${this.name} je ${food}.`;
10  }
11
12  sleep() {
13    return `${this.name} śpi.`;
14  }
15}
16
17// Klasa Dinosaur dziedzicząca po Animal
18class Dinosaur extends Animal {
19  constructor(name, species, diet) {
20    super(name); // Wywołanie konstruktora klasy bazowej
21    this.species = species;
22    this.diet = diet;
23    this.isExtinct = false;
24  }
25
26  roar() {
27    const volume = this.diet === "carnivore" ? "głośno" : "cicho";
28    return `${this.name} ryczy ${volume}!`;
29  }
30
31  // Nadpisujemy metodę eat z klasy bazowej
32  eat(food) {
33    if (this.diet === "carnivore" && food === "mięso") {
34      return `${this.name} z apetytem pożera ${food}!`;
35    } else if (this.diet === "herbivore" && food === "rośliny") {
36      return `${this.name} spokojnie skubie ${food}.`;
37    } else {
38      return `${this.name} nie jest zainteresowany jedzeniem ${food}.`;
39    }
40  }
41}
42
43// Klasa pochodna Velociraptor dziedzicząca po Dinosaur
44class Velociraptor extends Dinosaur {
45  constructor(name, speed) {
46    super(name, "Velociraptor", "carnivore");
47    this.speed = speed;
48    this.packHunter = true;
49  }
50
51  hunt() {
52    return `${this.name} poluje w grupie z prędkością ${this.speed} km/h!`;
53  }
54}
55
56// Tworzymy instancje
57const blue = new Velociraptor("Blue", 70);
58
59console.log(blue.eat("mięso")); // "Blue z apetytem pożera mięso!"
60console.log(blue.sleep()); // "Blue śpi."
61console.log(blue.roar()); // "Blue ryczy głośno!"
62console.log(blue.hunt()); // "Blue poluje w grupie z prędkością 70 km/h!"
63
64// Sprawdzamy łańcuch prototypów
65console.log(blue instanceof Velociraptor); // true
66console.log(blue instanceof Dinosaur); // true
67console.log(blue instanceof Animal); // true
68console.log(blue instanceof Object); // true

Składnia klas w ES6 jest tylko "cukrem składniowym" (syntactic sugar) nad istniejącym mechanizmem prototypów w JavaScript. Pod spodem, JavaScript nadal używa prototypów do implementacji dziedziczenia.

Wielodziedziczenie i miksy (mixins)

JavaScript nie obsługuje bezpośrednio wielodziedziczenia (dziedziczenia po wielu klasach jednocześnie), ale możemy używać wzorca "mixins" do uzyskania podobnego efektu:

1// Klasa bazowa
2class Dinosaur {
3  constructor(name, species) {
4    this.name = name;
5    this.species = species;
6  }
7
8  roar() {
9    return `${this.name} ryczy!`;
10  }
11}
12
13// Mixin dla zachowań drapieżnych (carnivore behavior)
14const CarnivoreMixin = {
15  hunt(prey) {
16    return `${this.name} poluje na ${prey}!`;
17  },
18
19  bite() {
20    return `${this.name} gryzie z dużą siłą!`;
21  }
22};
23
24// Mixin dla zachowań związanych ze stadnym polowaniem (pack hunting behavior)
25const PackHunterMixin = {
26  coordinateAttack() {
27    return `${this.name} koordynuje atak stada!`;
28  },
29
30  communicateWithPack() {
31    return `${this.name} komunikuje się z innymi członkami stada.`;
32  }
33};
34
35// Mixin dla zachowań związanych z terytorium (territorial behavior)
36const TerritorialMixin = {
37  markTerritory() {
38    return `${this.name} zaznacza swoje terytorium.`;
39  },
40
41  defendTerritory(intruder) {
42    return `${this.name} broni swojego terytorium przed ${intruder}!`;
43  }
44};
45
46// Funkcja do aplikowania mixinów do klasy
47function applyMixins(targetClass, ...mixins) {
48  mixins.forEach(mixin => {
49    Object.getOwnPropertyNames(mixin).forEach(name => {
50      if (name !== 'constructor') {
51        targetClass.prototype[name] = mixin[name];
52      }
53    });
54  });
55}
56
57// Klasa Velociraptor z kilkoma mixinami
58class Velociraptor extends Dinosaur {
59  constructor(name) {
60    super(name, "Velociraptor");
61    this.diet = "carnivore";
62    this.speed = 70; // km/h
63  }
64}
65
66// Aplikujemy mixiny do Velociraptor
67applyMixins(Velociraptor, CarnivoreMixin, PackHunterMixin, TerritorialMixin);
68
69// Tworzymy instancję Velociraptor
70const blue = new Velociraptor("Blue");
71
72// Teraz blue ma dostęp do metod z różnych mixinów
73console.log(blue.roar()); // "Blue ryczy!" (z Dinosaur)
74console.log(blue.hunt("Gallimimus")); // "Blue poluje na Gallimimus!" (z CarnivoreMixin)
75console.log(blue.bite()); // "Blue gryzie z dużą siłą!" (z CarnivoreMixin)
76console.log(blue.coordinateAttack()); // "Blue koordynuje atak stada!" (z PackHunterMixin)
77console.log(blue.defendTerritory("Tyrannosaurus")); // "Blue broni swojego terytorium przed Tyrannosaurus!" (z TerritorialMixin)

Mixiny pozwalają na "mieszanie" funkcjonalności z różnych źródeł bez konieczności stosowania wielodziedziczenia, którego JavaScript formalnie nie obsługuje.

Object.prototype

Wszystkie obiekty w JavaScript ostatecznie dziedziczą po

Object.prototype
. Zawiera on wiele przydatnych metod, takich jak
toString()
,
hasOwnProperty()
,
valueOf()
, itp.

1const dino = { name: "Rex" };
2
3// dino dziedziczy metody z Object.prototype
4console.log(dino.toString()); // "[object Object]"
5console.log(dino.hasOwnProperty("name")); // true
6console.log(dino.hasOwnProperty("age")); // false
7
8// Możemy nadpisać metody z Object.prototype
9dino.toString = function() {
10  return `Dinozaur: ${this.name}`;
11};
12
13console.log(dino.toString()); // "Dinozaur: Rex"

Dziedziczenie prototypowe w praktyce: System dinozaurów Parku Jurajskiego

Poniżej znajduje się przykład bardziej złożonego systemu zarządzania dinozaurami, wykorzystującego dziedziczenie prototypowe:

1// Podstawowa klasa dla wszystkich istot w parku
2class Creature {
3  constructor(name, age) {
4    this.name = name;
5    this.age = age;
6    this.health = 100;
7    this.alive = true;
8  }
9
10  eat(food) {
11    this.health = Math.min(100, this.health + 10);
12    return `${this.name} je ${food}. Zdrowie: ${this.health}%`;
13  }
14
15  sleep() {
16    this.health = Math.min(100, this.health + 5);
17    return `${this.name} śpi. Zdrowie: ${this.health}%`;
18  }
19
20  damage(amount) {
21    this.health = Math.max(0, this.health - amount);
22    if (this.health === 0 && this.alive) {
23      this.alive = false;
24      return `${this.name} nie żyje!`;
25    }
26    return `${this.name} otrzymuje ${amount} obrażeń. Pozostałe zdrowie: ${this.health}%`;
27  }
28}
29
30// Klasa dla dinozaurów, dziedzicząca po Creature
31class Dinosaur extends Creature {
32  constructor(name, species, age, diet) {
33    super(name, age);
34    this.species = species;
35    this.diet = diet;
36    this.containmentLevel = this.calculateContainmentLevel();
37  }
38
39  calculateContainmentLevel() {
40    if (this.diet === "carnivore") {
41      return this.age > 5 ? "Maximum" : "High";
42    } else {
43      return this.age > 10 ? "Medium" : "Standard";
44    }
45  }
46
47  roar() {
48    const volume = this.diet === "carnivore" ? "głośno" : "cicho";
49    return `${this.name} ryczy ${volume}!`;
50  }
51
52  getStatus() {
53    return `
54Dinozaur: ${this.name} (${this.species})
55Wiek: ${this.age} lat
56Dieta: ${this.diet === "carnivore" ? "Mięsożerna" : "Roślinożerna"}
57Zdrowie: ${this.health}%
58Status: ${this.alive ? "Żywy" : "Nieżywy"}
59Poziom bezpieczeństwa: ${this.containmentLevel}
60`;
61  }
62}
63
64// Klasa dla mięsożernych dinozaurów (drapieżników)
65class Carnivore extends Dinosaur {
66  constructor(name, species, age, attackPower) {
67    super(name, species, age, "carnivore");
68    this.attackPower = attackPower;
69    this.huntingSuccess = 0.7; // 70% szans na udane polowanie
70  }
71
72  hunt(prey) {
73    if (!this.alive) return `${this.name} nie żyje, nie może polować.`;
74    if (!prey.alive) return `${prey.name} jest już martwy.`;
75
76    const huntSuccess = Math.random() < this.huntingSuccess;
77
78    if (huntSuccess) {
79      const damage = Math.floor(this.attackPower * (0.7 + Math.random() * 0.6)); // 70-130% mocy ataku
80      const result = prey.damage(damage);
81      this.eat("mięso");
82      return `${this.name} skutecznie poluje na ${prey.name}! ${result}`;
83    } else {
84      return `${this.name} próbuje upolować ${prey.name}, ale nie udaje się.`;
85    }
86  }
87
88  clawAttack(target) {
89    if (!this.alive) return `${this.name} nie żyje, nie może atakować.`;
90    if (!target.alive) return `${target.name} jest już martwy.`;
91
92    const damage = Math.floor(this.attackPower * 0.6);
93    const result = target.damage(damage);
94    return `${this.name} atakuje ${target.name} pazurami! ${result}`;
95  }
96
97  biteAttack(target) {
98    if (!this.alive) return `${this.name} nie żyje, nie może atakować.`;
99    if (!target.alive) return `${target.name} jest już martwy.`;
100
101    const damage = Math.floor(this.attackPower * 1.2);
102    const result = target.damage(damage);
103    return `${this.name} gryzie ${target.name}! ${result}`;
104  }
105}
106
107// Klasa dla roślinożernych dinozaurów
108class Herbivore extends Dinosaur {
109  constructor(name, species, age, defenseRating) {
110    super(name, species, age, "herbivore");
111    this.defenseRating = defenseRating;
112    this.fleeSuccess = 0.6; // 60% szans na udaną ucieczkę
113  }
114
115  grazeVegetation() {
116    if (!this.alive) return `${this.name} nie żyje, nie może jeść.`;
117
118    this.health = Math.min(100, this.health + 15);
119    return `${this.name} skubie rośliny. Zdrowie wzrasta do ${this.health}%.`;
120  }
121
122  flee(predator) {
123    if (!this.alive) return `${this.name} nie żyje, nie może uciekać.`;
124
125    const escapeSuccess = Math.random() < this.fleeSuccess;
126
127    if (escapeSuccess) {
128      this.health = Math.max(0, this.health - 5); // Ucieczka kosztuje energię
129      return `${this.name} skutecznie ucieka przed ${predator.name}!`;
130    } else {
131      return `${this.name} próbuje uciec przed ${predator.name}, ale nie udaje się.`;
132    }
133  }
134
135  // Defensywna metoda - zmniejsza otrzymane obrażenia
136  damage(amount) {
137    // Roślinożercy otrzymują mniej obrażeń dzięki defenseRating
138    const reducedDamage = Math.max(0, amount - this.defenseRating);
139    return super.damage(reducedDamage);
140  }
141}
142
143// Specyficzne gatunki dinozaurów
144class Tyrannosaurus extends Carnivore {
145  constructor(name, age) {
146    super(name, "Tyrannosaurus Rex", age, 35);
147    this.intimidationFactor = 9;
148    this.territorialRange = 5; // km
149  }
150
151  intimidate(target) {
152    return `${this.name} wydaje ogłuszający ryk, który przeraża ${target.name}!`;
153  }
154
155  // Nadpisujemy metodę roar
156  roar() {
157    return `${this.name} wydaje potężny, ogłuszający ryk, który rozchodzi się na kilometry!`;
158  }
159}
160
161class Velociraptor extends Carnivore {
162  constructor(name, age) {
163    super(name, "Velociraptor", age, 20);
164    this.speed = 70; // km/h
165    this.intelligence = 9;
166    this.packHunting = true;
167  }
168
169  coordinateAttack(targets) {
170    if (!Array.isArray(targets) || targets.length < 2) {
171      return `${this.name} potrzebuje co najmniej dwóch celów do koordynacji ataku.`;
172    }
173
174    let results = `${this.name} koordynuje atak stada na ${targets.map(t => t.name).join(', ')}!
175`;
176    targets.forEach(target => {
177      const damage = Math.floor(this.attackPower * 1.5); // Wzmocniony atak stadny
178      results += target.damage(damage) + '
179';
180    });
181
182    return results;
183  }
184
185  ambush(target) {
186    const damage = Math.floor(this.attackPower * 2); // Podwójne obrażenia z zasadzki
187    const result = target.damage(damage);
188    return `${this.name} urządza zasadzkę na ${target.name}! ${result}`;
189  }
190}
191
192class Triceratops extends Herbivore {
193  constructor(name, age) {
194    super(name, "Triceratops", age, 25);
195    this.hornStrength = 30;
196  }
197
198  chargeAttack(target) {
199    const damage = this.hornStrength + Math.floor(Math.random() * 10);
200    const result = target.damage(damage);
201    return `${this.name} szarżuje na ${target.name} swoimi rogami! ${result}`;
202  }
203
204  defendFromAttack(attacker) {
205    const counterDamage = Math.floor(this.hornStrength * 0.5);
206    const result = attacker.damage(counterDamage);
207    return `${this.name} broni się przed ${attacker.name} używając swoich rogów! ${result}`;
208  }
209}
210
211class Brachiosaurus extends Herbivore {
212  constructor(name, age) {
213    super(name, "Brachiosaurus", age, 15);
214    this.height = 13; // metry
215    this.tailLength = 9; // metry
216  }
217
218  reachHighVegetation() {
219    this.health = Math.min(100, this.health + 20);
220    return `${this.name} sięga swoją długą szyją po liście wysokich drzew. Zdrowie wzrasta do ${this.health}%.`;
221  }
222
223  tailWhip(target) {
224    const damage = Math.floor(this.tailLength * 2);
225    const result = target.damage(damage);
226    return `${this.name} uderza ${target.name} swoim ogromnym ogonem! ${result}`;
227  }
228}
229
230// Symulacja parku jurajskiego
231function runJurassicParkSimulation() {
232  console.log("=== SYMULACJA PARKU JURAJSKIEGO ===
233");
234
235  // Tworzenie dinozaurów
236  const rex = new Tyrannosaurus("Rex", 8);
237  const blue = new Velociraptor("Blue", 4);
238  const delta = new Velociraptor("Delta", 4);
239  const trixie = new Triceratops("Trixie", 12);
240  const bronty = new Brachiosaurus("Bronty", 25);
241
242  // Wyświetlanie informacji o dinozaurach
243  console.log(rex.getStatus());
244  console.log(blue.getStatus());
245  console.log(trixie.getStatus());
246  console.log(bronty.getStatus());
247
248  console.log("
249=== INTERAKCJE DINOZAURÓW ===
250");
251
252  // Interakcje między dinozaurami
253  console.log(rex.roar());
254  console.log(blue.roar());
255  console.log(trixie.grazeVegetation());
256  console.log(bronty.reachHighVegetation());
257
258  console.log("
259=== POJEDYNEK: REX VS TRIXIE ===
260");
261
262  console.log(rex.intimidate(trixie));
263  console.log(trixie.flee(rex));
264  console.log(rex.hunt(trixie));
265  console.log(trixie.defendFromAttack(rex));
266  console.log(rex.biteAttack(trixie));
267
268  console.log("
269=== POLOWANIE STADA VELOCIRAPTORÓW ===
270");
271
272  console.log(blue.coordinateAttack([trixie, bronty]));
273  console.log(delta.ambush(bronty));
274  console.log(bronty.tailWhip(delta));
275
276  console.log("
277=== STAN DINOZAURÓW PO INTERAKCJACH ===
278");
279
280  console.log(rex.getStatus());
281  console.log(blue.getStatus());
282  console.log(delta.getStatus());
283  console.log(trixie.getStatus());
284  console.log(bronty.getStatus());
285}
286
287// Uruchomienie symulacji
288runJurassicParkSimulation();

Dziedziczenie prototypowe vs klasyczne dziedziczenie

Dziedziczenie prototypowe w JavaScript różni się od klasycznego dziedziczenia w innych językach obiektowych:

  1. Dynamiczność - prototypy mogą być modyfikowane w czasie wykonania programu, podczas gdy klasy są zwykle statyczne.
  2. Łańcuch prototypów - właściwości są wyszukiwane w łańcuchu prototypów, podczas gdy w klasycznym dziedziczeniu metody są rozwiązywane przez hierarchię klas.
  3. Brak klas (przed ES6) - JavaScript tradycyjnie nie miał formalnej koncepcji klas, a jedynie funkcje konstruktora i prototypy.
  4. Dziedziczenie pojedyncze - JavaScript obsługuje tylko dziedziczenie pojedyncze (każdy obiekt ma tylko jeden prototyp), podczas gdy niektóre języki obsługują wielodziedziczenie.

Dobre praktyki przy pracy z prototypami

  1. Zrozumienie mechanizmu - ważne jest zrozumienie, jak działa łańcuch prototypów, aby skutecznie korzystać z dziedziczenia.
  2. Korzystanie z klas (ES6+) - dla większości przypadków użycia składnia klas jest bardziej czytelna i łatwiejsza w utrzymaniu.
  3. Unikanie modyfikacji wbudowanych prototypów - modyfikacja prototypów wbudowanych obiektów (np. Object.prototype, Array.prototype) może prowadzić do nieoczekiwanych konfliktów.
  4. Stosowanie Object.create() dla dziedziczenia - przy używaniu tradycyjnego podejścia, Object.create() jest czystym sposobem implementacji dziedziczenia prototypowego.
  5. Kompozycja nad dziedziczeniem - czasami lepiej jest używać kompozycji (łączenia obiektów) zamiast głębokiego dziedziczenia, aby uniknąć problemów z hierarchią.

Deno i Bun - nowoczesne alternatywy dla Node.js

Podobnie jak Park Jurajski eksperymentował z różnymi metodami klonowania dinozaurów, świat JavaScript również ewoluuje, oferując nowe środowiska uruchomieniowe dla kodu po stronie serwera. Oprócz Node.js, który przez lata był standardem de facto, pojawiły się nowe, ciekawe alternatywy: Deno i Bun.

Deno - bezpieczniejsza przyszłość JavaScript

Geneza Deno

Deno został stworzony przez Ryana Dahla - tego samego programistę, który stworzył Node.js! W 2018 roku, podczas prezentacji zatytułowanej "10 rzeczy, których żałuję w Node.js", Ryan przedstawił swoją wizję lepszego środowiska uruchomieniowego dla JavaScript i TypeScript. To jak dr Hammond w Parku Jurajskim, który po pierwszych niepowodzeniach próbował stworzyć doskonalsze, bezpieczniejsze rozwiązanie.

Kluczowe różnice między Deno a Node.js

1. TypeScript natywnie

Deno obsługuje TypeScript "out of the box" - nie potrzebujesz dodatkowej konfiguracji ani transpilacji:

1// dinosaur.ts
2interface Dinosaur {
3  name: string;
4  species: string;
5  age: number;
6  dangerous: boolean;
7}
8
9function createDinosaur(name: string, species: string): Dinosaur {
10  return {
11    name,
12    species,
13    age: 0,
14    dangerous: species === "Tyrannosaurus Rex"
15  };
16}
17
18const rex = createDinosaur("Rexy", "Tyrannosaurus Rex");
19console.log(rex);

Uruchamiasz to bezpośrednio:

1deno run dinosaur.ts

2. System uprawnień (Permissions)

Największą innowacją Deno jest system uprawnień. W Node.js każdy skrypt ma pełny dostęp do systemu plików, sieci i zmiennych środowiskowych. Deno wymaga jawnego nadania uprawnień - to jak systemy bezpieczeństwa w Parku Jurajskim, które kontrolują dostęp do różnych stref:

1# Bez uprawnień - skrypt nie może nic zrobić
2deno run script.ts
3
4# Z dostępem do sieci
5deno run --allow-net script.ts
6
7# Z dostępem do plików w określonym katalogu
8deno run --allow-read=/data --allow-write=/data script.ts
9
10# Z dostępem do zmiennych środowiskowych
11deno run --allow-env script.ts
12
13# Wszystkie uprawnienia (używaj ostrożnie!)
14deno run --allow-all script.ts

Przykład z systemem zabezpieczeń parku:

1// security-monitor.ts
2// Ten skrypt monitoruje stan zabezpieczeń i zapisuje logi
3
4// Próba odczytu czujników bez uprawnień
5try {
6  const sensors = await fetch("http://park-sensors.internal/status");
7  const data = await sensors.json();
8  console.log("Status czujników:", data);
9} catch (error) {
10  console.error("Brak uprawnień do sieci:", error.message);
11}
12
13// Próba zapisu logów bez uprawnień
14try {
15  await Deno.writeTextFile("./logs/security.log", "System sprawny
16");
17} catch (error) {
18  console.error("Brak uprawnień do zapisu:", error.message);
19}

Uruchomienie:

1# To się nie powiedzie - brak uprawnień
2deno run security-monitor.ts
3
4# To zadziała
5deno run --allow-net --allow-write=./logs security-monitor.ts

3. Brak node_modules

Deno nie używa

node_modules
. Zamiast tego importuje moduły bezpośrednio z URL-i i cache'uje je lokalnie:

1// Import z URL
2import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
3
4// Serwer HTTP obsługujący zapytania o dinozaury
5const handler = (req: Request): Response => {
6  const dinosaurs = [
7    { name: "Rexy", species: "T-Rex" },
8    { name: "Blue", species: "Velociraptor" }
9  ];
10
11  return new Response(JSON.stringify(dinosaurs), {
12    headers: { "content-type": "application/json" }
13  });
14};
15
16serve(handler, { port: 8000 });

4. Standardowa biblioteka

Deno ma dobrze zaprojektowaną standardową bibliotekę, podobną do Go czy Pythona. Nie musisz instalować dziesiątek pakietów dla podstawowych operacji:

1// Praca z plikami
2import { readLines } from "https://deno.land/std@0.200.0/io/mod.ts";
3
4// Czytanie logów dinozaurów linia po linii
5const file = await Deno.open("dinosaur-logs.txt");
6for await (const line of readLines(file)) {
7  console.log("Log entry:", line);
8}
9file.close();
10
11// Praca z UUID
12import { v4 } from "https://deno.land/std@0.200.0/uuid/mod.ts";
13
14const dinosaurId = v4.generate();
15console.log("Nowy ID dinozaura:", dinosaurId);

5. Wbudowane narzędzia

Deno ma wbudowane narzędzia, które w Node.js wymagałyby dodatkowych pakietów:

1# Formatowanie kodu
2deno fmt script.ts
3
4# Linting
5deno lint script.ts
6
7# Testy
8deno test tests/
9
10# Bundling
11deno bundle main.ts output.js
12
13# Dokumentacja
14deno doc module.ts

Kiedy rozważyć użycie Deno?

Używaj Deno, gdy:

  • Zaczynasz nowy projekt i nie masz legacy kodu Node.js
  • Bezpieczeństwo jest priorytetem (system uprawnień)
  • Chcesz używać TypeScript bez dodatkowej konfiguracji
  • Preferujesz prostszą architekturę bez node_modules
  • Tworzysz narzędzia CLI lub skrypty automatyzacyjne

Zostań przy Node.js, gdy:

  • Masz duży istniejący projekt Node.js
  • Potrzebujesz specyficznych pakietów npm, które nie są kompatybilne z Deno
  • Pracujesz w zespole, który zna tylko Node.js
  • Korzystasz z frameworków silnie związanych z Node.js (np. niektóre wersje Express)

Bun - najszybsze środowisko JavaScript

Bun to kolejna nowoczesna alternatywa, która skupia się na wydajności. Napisana w Zig zamiast C++ (jak Node.js i Deno), oferuje imponującą szybkość:

1// Bun obsługuje TypeScript natywnie
2import { serve } from "bun";
3
4serve({
5  port: 3000,
6  fetch(req) {
7    return new Response("Witaj w systemie zarządzania Parkiem Jurajskim!");
8  }
9});

Kluczowe cechy Bun:

1. Błyskawiczna szybkość

  • Start aplikacji: ~3x szybszy niż Node.js
  • Instalacja pakietów: ~100x szybsza niż npm
  • Bundling: ~10x szybszy niż webpack

2. Kompatybilność z Node.js

  • Obsługuje moduły npm
  • Ma wbudowane API kompatybilne z Node.js
  • Łatwiejsza migracja niż w przypadku Deno

3. Wbudowane narzędzia

1# Bundler
2bun build ./index.ts --outdir ./dist
3
4# Test runner
5bun test
6
7# Package manager (super szybki!)
8bun install
9bun add lodash

4. Wbudowany SQLite

1import { Database } from "bun:sqlite";
2
3const db = new Database("dinosaurs.db");
4
5// Tworzenie tabeli
6db.run(`
7  CREATE TABLE IF NOT EXISTS dinosaurs (
8    id INTEGER PRIMARY KEY,
9    name TEXT,
10    species TEXT,
11    age INTEGER
12  )
13`);
14
15// Wstawianie danych
16const insert = db.prepare("INSERT INTO dinosaurs (name, species, age) VALUES (?, ?, ?)");
17insert.run("Rexy", "T-Rex", 8);
18insert.run("Blue", "Velociraptor", 4);
19
20// Zapytania
21const dinosaurs = db.query("SELECT * FROM dinosaurs").all();
22console.log(dinosaurs);

Porównanie: Node.js vs Deno vs Bun

| Cecha | Node.js | Deno | Bun | |-------|---------|------|-----| | TypeScript | Wymaga konfiguracji | Natywnie | Natywnie | | Package manager | npm/yarn/pnpm | Wbudowany | Super szybki | | System uprawnień | Brak | Tak | Brak | | Kompatybilność npm | 100% | ~Częściowa | Wysoka | | Wydajność | Standardowa | Dobra | Doskonała | | Ekosystem | Ogromny | Rosnący | Młody | | Stabilność | Produkcyjna | Stabilna | Beta/Rozwój |

Praktyczny przykład - API dla Parku Jurajskiego w każdym środowisku

Node.js + Express:

1// Wymaga: npm install express
2const express = require('express');
3const app = express();
4
5app.get('/dinosaurs', (req, res) => {
6  res.json([
7    { name: "Rexy", species: "T-Rex" },
8    { name: "Blue", species: "Velociraptor" }
9  ]);
10});
11
12app.listen(3000, () => console.log('Server running on port 3000'));

Deno:

1// Brak instalacji - wszystko z URL
2import { serve } from "https://deno.land/std@0.200.0/http/server.ts";
3
4serve((req) => {
5  const dinosaurs = [
6    { name: "Rexy", species: "T-Rex" },
7    { name: "Blue", species: "Velociraptor" }
8  ];
9
10  return new Response(JSON.stringify(dinosaurs), {
11    headers: { "content-type": "application/json" }
12  });
13}, { port: 3000 });
14
15console.log("Server running on port 3000");

Bun:

1// Wbudowane API, super szybkie
2Bun.serve({
3  port: 3000,
4  fetch(req) {
5    const dinosaurs = [
6      { name: "Rexy", species: "T-Rex" },
7      { name: "Blue", species: "Velociraptor" }
8    ];
9
10    return new Response(JSON.stringify(dinosaurs), {
11      headers: { "content-type": "application/json" }
12    });
13  }
14});
15
16console.log("Server running on port 3000");

Podsumowanie alternatyw

Podobnie jak Park Jurajski eksperymentował z różnymi metodami, aby znaleźć najlepsze rozwiązania, świat JavaScript oferuje różne środowiska uruchomieniowe, każde z własnymi zaletami:

  • Node.js - sprawdzony, stabilny, z ogromnym ekosystemem
  • Deno - bezpieczny, nowoczesny, z TypeScript natywnie
  • Bun - błyskawicznie szybki, kompatybilny, z wbudowanymi narzędziami

Wybór zależy od twoich potrzeb, ale znajomość alternatyw sprawia, że jesteś lepiej przygotowany na przyszłość JavaScript!

Podsumowanie

Prototypy i dziedziczenie prototypowe są fundamentalnymi koncepcjami w JavaScript. Nawet gdy używamy nowoczesnej składni klas (ES6+), ważne jest zrozumienie leżącego u podstaw mechanizmu prototypów, aby w pełni wykorzystać możliwości języka. W naszym Parku Jurajskim dziedziczenie prototypowe pomaga nam modelować różne typy dinozaurów i ich zachowań w spójny, wydajny sposób.

Główne koncepcje, które omawialiśmy, to:

  1. Prototypy - mechanizm, dzięki któremu obiekty mogą dziedziczyć właściwości i metody od innych obiektów.
  2. Łańcuch prototypów - sekwencja obiektów połączonych przez ich prototypy, którą JavaScript przeszukuje, gdy próbuje uzyskać dostęp do właściwości.
  3. Funkcje konstruktora i prototypy - tradycyjny sposób implementacji dziedziczenia w JavaScript.
  4. Klasy ES6+ - nowoczesna składnia dla dziedziczenia, oparta na mechanizmie prototypów.
  5. Mixiny - sposób na łączenie funkcjonalności z różnych źródeł.

Zrozumienie prototypów i dziedziczenia w JavaScript jest kluczem do pisania efektywnego, dobrze zorganizowanego kodu, który może modelować złożone relacje między obiektami, takie jak te w naszym wirtualnym Parku Jurajskim.

Vai a CodeWorlds