Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Właściwości i metody obiektów

W systemie zarządzania Parkiem Jurajskim, właściwości i metody obiektów są kluczowe dla modelowania i kontrolowania zachowania różnych elementów naszego kompleksu. W tym module zagłębimy się bardziej szczegółowo w właściwości i metody obiektów JavaScript, poznając zaawansowane techniki, które pomagają nam utrzymać nasz park i jego mieszkańców w idealnym stanie.

Deskryptory właściwości

W JavaScript każda właściwość obiektu ma nie tylko wartość, ale również atrybuty kontrolujące, jak ta właściwość może być używana. Te atrybuty to tzw. deskryptory właściwości.

1const dinosaur = {
2  name: "Blue"
3};
4
5// Pobieranie deskryptora właściwości
6const descriptor = Object.getOwnPropertyDescriptor(dinosaur, "name");
7console.log(descriptor);
8// Wynik: { value: "Blue", writable: true, enumerable: true, configurable: true }

Deskryptor właściwości zawiera następujące pola:

  • value: aktualna wartość właściwości
  • writable: czy właściwość może być zmieniona
  • enumerable: czy właściwość pojawia się podczas iteracji (np. w pętli for...in)
  • configurable: czy deskryptor może być zmieniony i czy właściwość może być usunięta

Definiowanie właściwości z deskryptorami

Możemy zdefiniować nowe właściwości z określonymi deskryptorami za pomocą

Object.defineProperty()
:

1const velociraptor = {};
2
3// Definiujemy właściwość "species" z określonymi deskryptorami
4Object.defineProperty(velociraptor, "species", {
5  value: "Velociraptor",
6  writable: false, // Nie możemy zmienić wartości
7  enumerable: true, // Pojawi się podczas iteracji
8  configurable: false // Nie możemy zmienić deskryptora ani usunąć właściwości
9});
10
11console.log(velociraptor.species); // "Velociraptor"
12
13// Próba zmiany nie zadziała (w trybie ścisłym rzuci błąd)
14velociraptor.species = "Tyrannosaurus";
15console.log(velociraptor.species); // Nadal "Velociraptor"
16
17// Próba usunięcia nie zadziała
18delete velociraptor.species; // Zwróci false
19console.log(velociraptor.species); // Nadal "Velociraptor"

Możemy również zdefiniować wiele właściwości naraz:

1const triceratops = {};
2
3Object.defineProperties(triceratops, {
4  species: {
5    value: "Triceratops",
6    writable: false
7  },
8  diet: {
9    value: "herbivore",
10    writable: false
11  },
12  age: {
13    value: 5,
14    writable: true // Wiek może się zmieniać
15  }
16});
17
18console.log(triceratops.species); // "Triceratops"
19console.log(triceratops.diet); // "herbivore"
20console.log(triceratops.age); // 5
21
22triceratops.age = 6;
23console.log(triceratops.age); // 6

Gettery i settery

Gettery i settery to specjalne metody, które pozwalają na kontrolę dostępu do właściwości obiektu.

1const dinosaur = {
2  _health: 100, // Konwencja podkreślenia wskazuje, że to prywatna właściwość
3
4  // Getter - wykonuje się, gdy próbujemy odczytać właściwość health
5  get health() {
6    console.log("Odczytywanie poziomu zdrowia");
7    return this._health;
8  },
9
10  // Setter - wykonuje się, gdy próbujemy przypisać wartość do właściwości health
11  set health(value) {
12    if (value < 0) {
13      console.log("Ostrzeżenie: Próba ustawienia zdrowia poniżej 0");
14      this._health = 0;
15    } else if (value > 100) {
16      console.log("Ostrzeżenie: Próba ustawienia zdrowia powyżej 100");
17      this._health = 100;
18    } else {
19      console.log(`Ustawianie poziomu zdrowia na ${value}`);
20      this._health = value;
21    }
22  }
23};
24
25console.log(dinosaur.health); // Uruchamia getter, wynik: 100
26dinosaur.health = 80; // Uruchamia setter
27console.log(dinosaur.health); // 80
28dinosaur.health = 150; // "Ostrzeżenie: Próba ustawienia zdrowia powyżej 100"
29console.log(dinosaur.health); // 100
30dinosaur.health = -20; // "Ostrzeżenie: Próba ustawienia zdrowia poniżej 0"
31console.log(dinosaur.health); // 0

Gettery i settery możemy również zdefiniować przy użyciu

Object.defineProperty()
:

1const dinosaur = {
2  _name: "Rex",
3  _species: "Tyrannosaurus"
4};
5
6Object.defineProperty(dinosaur, "fullName", {
7  get: function() {
8    return `${this._name} the ${this._species}`;
9  },
10
11  set: function(value) {
12    // Zakładamy, że value ma format "Nazwa the Gatunek"
13    const parts = value.split(" the ");
14    if (parts.length === 2) {
15      this._name = parts[0];
16      this._species = parts[1];
17    } else {
18      console.log("Nieprawidłowy format. Użyj: 'Nazwa the Gatunek'");
19    }
20  },
21
22  enumerable: true,
23  configurable: true
24});
25
26console.log(dinosaur.fullName); // "Rex the Tyrannosaurus"
27dinosaur.fullName = "Blue the Velociraptor";
28console.log(dinosaur._name); // "Blue"
29console.log(dinosaur._species); // "Velociraptor"

Metody obiektów i ich zaawansowane właściwości

Wiązanie this - metoda bind

Słowo kluczowe

this
w metodach odnosi się do obiektu, na którym metoda jest wywoływana. Jednak w niektórych przypadkach (np. gdy przekazujemy metodę jako callback) możemy stracić kontekst
this
. Metoda
bind
pozwala na jawne ustawienie wartości
this
.

1const rex = {
2  name: "Rex",
3  makeSound: function() {
4    console.log(`${this.name} ryczy głośno!`);
5  }
6};
7
8// Działanie normalne
9rex.makeSound(); // "Rex ryczy głośno!"
10
11// Problematyczne użycie - utrata kontekstu this
12const rexSound = rex.makeSound;
13// rexSound(); // Błąd: this.name jest undefined
14
15// Rozwiązanie - użycie bind, aby ustawić wartość this
16const boundRexSound = rex.makeSound.bind(rex);
17boundRexSound(); // "Rex ryczy głośno!"
18
19// Możemy również ustawić this na inny obiekt
20const blue = { name: "Blue" };
21const blueSound = rex.makeSound.bind(blue);
22blueSound(); // "Blue ryczy głośno!"

Metody call i apply

Metody

call
i
apply
pozwalają na wywołanie funkcji z określoną wartością
this
i argumentami:

1function attack(target, damage) {
2  console.log(`${this.name} atakuje ${target} i zadaje ${damage} obrażeń!`);
3}
4
5const trex = { name: "Tyrannosaurus Rex" };
6const raptor = { name: "Velociraptor" };
7
8// Używamy call z jawnie przekazywanymi argumentami
9attack.call(trex, "Triceratops", 50);
10// "Tyrannosaurus Rex atakuje Triceratops i zadaje 50 obrażeń!"
11
12// Używamy apply z argumentami w tablicy
13attack.apply(raptor, ["Gallimimus", 30]);
14// "Velociraptor atakuje Gallimimus i zadaje 30 obrażeń!"

Właściwości obiektów jako symbole

Symbole zapewniają unikalne identyfikatory, które mogą być używane jako klucze właściwości obiektów. Są one szczególnie przydatne do tworzenia "prywatnych" właściwości lub do rozszerzania obiektów bez ryzyka kolizji nazw.

1// Tworzenie symboli
2const healthSymbol = Symbol('health');
3const nameSymbol = Symbol('name');
4
5const dinosaur = {
6  [nameSymbol]: "Blue",
7  [healthSymbol]: 100,
8  species: "Velociraptor"
9};
10
11console.log(dinosaur[nameSymbol]); // "Blue"
12console.log(dinosaur[healthSymbol]); // 100
13
14// Symbole nie są widoczne w pętlach for...in
15for (let key in dinosaur) {
16  console.log(key); // Tylko "species", bez nameSymbol i healthSymbol
17}
18
19// Ani w Object.keys()
20console.log(Object.keys(dinosaur)); // ["species"]
21
22// Ale można je uzyskać za pomocą Object.getOwnPropertySymbols()
23console.log(Object.getOwnPropertySymbols(dinosaur)); // [Symbol(name), Symbol(health)]

Dobrze znane symbole

JavaScript definiuje kilka "dobrze znanych symboli", które pozwalają klasom na dostosowanie swojego zachowania. Najczęściej używanym jest

Symbol.iterator
, który umożliwia obiektom bycie iterowalnymi.

1// Tworzymy obiekt DinosaurPack, który będzie iterowalny
2class DinosaurPack {
3  constructor() {
4    this.dinosaurs = [];
5  }
6
7  addDinosaur(name, species) {
8    this.dinosaurs.push({ name, species });
9  }
10
11  // Implementacja iterowalności
12  [Symbol.iterator]() {
13    let index = 0;
14    const dinosaurs = this.dinosaurs;
15
16    return {
17      next: function() {
18        if (index < dinosaurs.length) {
19          return { value: dinosaurs[index++], done: false };
20        } else {
21          return { done: true };
22        }
23      }
24    };
25  }
26}
27
28const pack = new DinosaurPack();
29pack.addDinosaur("Blue", "Velociraptor");
30pack.addDinosaur("Delta", "Velociraptor");
31pack.addDinosaur("Echo", "Velociraptor");
32pack.addDinosaur("Charlie", "Velociraptor");
33
34// Teraz możemy iterować po naszym obiekcie
35for (const dino of pack) {
36  console.log(`${dino.name} to ${dino.species}`);
37}
38// Wynik:
39// "Blue to Velociraptor"
40// "Delta to Velociraptor"
41// "Echo to Velociraptor"
42// "Charlie to Velociraptor"

Zamrożenie, uszczelnienie i zapobieganie rozszerzaniu obiektów

JavaScript oferuje trzy metody do kontrolowania modyfikowalności obiektów:

Object.freeze()

Kompletnie zamraża obiekt - nie można zmieniać istniejących właściwości ani dodawać nowych.

1const securityProtocol = {
2  level: "Maximum",
3  status: "Active",
4  lastUpdated: "2023-01-15"
5};
6
7// Zamrażamy obiekt
8Object.freeze(securityProtocol);
9
10// Próby modyfikacji nie zadziałają
11securityProtocol.level = "Minimum"; // Nie zmieni się
12securityProtocol.newProperty = "Value"; // Nie zostanie dodane
13
14console.log(securityProtocol.level); // Nadal "Maximum"
15console.log(securityProtocol.newProperty); // undefined
16
17console.log(Object.isFrozen(securityProtocol)); // true

Object.seal()

Uszczelnia obiekt - można modyfikować istniejące właściwości, ale nie można dodawać nowych ani usuwać istniejących.

1const dinoStats = {
2  health: 100,
3  hunger: 50
4};
5
6// Uszczelniamy obiekt
7Object.seal(dinoStats);
8
9// Możemy modyfikować istniejące właściwości
10dinoStats.health = 80;
11console.log(dinoStats.health); // 80
12
13// Ale nie możemy dodawać nowych
14dinoStats.happiness = 90; // Nie zadziała
15console.log(dinoStats.happiness); // undefined
16
17// Ani usuwać istniejących
18delete dinoStats.hunger; // Nie zadziała
19console.log(dinoStats.hunger); // Nadal 50
20
21console.log(Object.isSealed(dinoStats)); // true

Object.preventExtensions()

Zapobiega rozszerzaniu obiektu - nie można dodawać nowych właściwości, ale można modyfikować i usuwać istniejące.

1const dinosaur = {
2  name: "Blue",
3  species: "Velociraptor"
4};
5
6// Zapobiegamy rozszerzaniu
7Object.preventExtensions(dinosaur);
8
9// Nie możemy dodawać nowych właściwości
10dinosaur.age = 4; // Nie zadziała
11console.log(dinosaur.age); // undefined
12
13// Ale możemy modyfikować istniejące
14dinosaur.name = "Delta";
15console.log(dinosaur.name); // "Delta"
16
17// I usuwać istniejące
18delete dinosaur.species;
19console.log(dinosaur.species); // undefined
20
21console.log(Object.isExtensible(dinosaur)); // false

Metody proksujące

Metody proxy pozwalają na przechwytywanie i dostosowywanie podstawowych operacji na obiektach.

1const dinosaur = {
2  name: "Blue",
3  species: "Velociraptor",
4  health: 100,
5  hunger: 50,
6  aggressionLevel: 8
7};
8
9// Tworzymy proxy dla obiektu dinosaur
10const monitoredDinosaur = new Proxy(dinosaur, {
11  // Przechwytujemy operację pobierania właściwości
12  get: function(target, prop, receiver) {
13    console.log(`Odczytywanie właściwości: ${prop}`);
14    return Reflect.get(target, prop, receiver);
15  },
16
17  // Przechwytujemy operację ustawiania właściwości
18  set: function(target, prop, value, receiver) {
19    console.log(`Ustawianie ${prop} na ${value}`);
20
21    // Dodajemy walidację dla niektórych właściwości
22    if (prop === 'health') {
23      if (value < 0) value = 0;
24      if (value > 100) value = 100;
25
26      // Logowanie zmiany zdrowia
27      const oldValue = target[prop];
28      const change = value - oldValue;
29      console.log(`Zmiana zdrowia: ${change > 0 ? '+' : ''}${change}%`);
30
31      // Sprawdzanie czy dinozaur nie jest poważnie ranny
32      if (value < 20 && oldValue >= 20) {
33        console.log("ALARM: Dinozaur jest poważnie ranny!");
34      }
35    }
36
37    if (prop === 'aggressionLevel') {
38      if (value > 7) {
39        console.log("UWAGA: Wysoki poziom agresji - należy zwiększyć środki bezpieczeństwa!");
40      }
41    }
42
43    return Reflect.set(target, prop, value, receiver);
44  },
45
46  // Przechwytujemy operację usuwania właściwości
47  deleteProperty: function(target, prop) {
48    console.log(`Próba usunięcia ${prop} - operacja zabroniona dla bezpieczeństwa danych`);
49    return false; // Nie pozwalamy na usuwanie właściwości
50  }
51});
52
53// Testujemy nasze proxy
54console.log(monitoredDinosaur.name); // "Odczytywanie właściwości: name", "Blue"
55monitoredDinosaur.health = 90; // "Ustawianie health na 90", "Zmiana zdrowia: -10%"
56monitoredDinosaur.health = 15; // "Ustawianie health na 15", "Zmiana zdrowia: -75%", "ALARM: Dinozaur jest poważnie ranny!"
57monitoredDinosaur.aggressionLevel = 9; // "Ustawianie aggressionLevel na 9", "UWAGA: Wysoki poziom agresji..."
58delete monitoredDinosaur.hunger; // "Próba usunięcia hunger - operacja zabroniona dla bezpieczeństwa danych"

Praktyczne zastosowanie: System monitorowania zdrowia dinozaurów

Połączmy kilka poznanych technik, aby stworzyć zaawansowany system monitorowania zdrowia dinozaurów w naszym parku:

1class DinosaurHealthMonitor {
2  constructor() {
3    this.dinosaurs = new Map();
4    this.alarmThreshold = 30;
5    this.criticalThreshold = 15;
6    this.lastCheckTime = null;
7  }
8
9  // Dodajemy dinozaura do systemu monitorowania
10  addDinosaur(id, name, species) {
11    if (this.dinosaurs.has(id)) {
12      console.log(`Dinozaur o ID ${id} już istnieje w systemie.`);
13      return false;
14    }
15
16    const dinosaurData = {
17      id,
18      name,
19      species,
20      _health: 100,
21      _lastFed: new Date(),
22      _status: "Healthy",
23
24      // Używamy gettera i settera dla zdrowia
25      get health() {
26        return this._health;
27      },
28
29      set health(value) {
30        const oldHealth = this._health;
31        this._health = Math.max(0, Math.min(100, value)); // Ograniczenie do zakresu 0-100
32
33        // Aktualizacja statusu na podstawie zdrowia
34        if (this._health === 0) {
35          this._status = "Critical - Emergency Response Required";
36        } else if (this._health < 15) {
37          this._status = "Critical";
38        } else if (this._health < 30) {
39          this._status = "Poor - Medical Attention Required";
40        } else if (this._health < 60) {
41          this._status = "Fair";
42        } else if (this._health < 90) {
43          this._status = "Good";
44        } else {
45          this._status = "Excellent";
46        }
47
48        return this._health;
49      },
50
51      // Metoda karmienia dinozaura
52      feed() {
53        const now = new Date();
54        const hoursSinceLastFed = (now - this._lastFed) / (1000 * 60 * 60);
55
56        // Efekt karmienia zależy od tego, jak dawno dinozaur był ostatnio karmiony
57        if (hoursSinceLastFed < 1) {
58          console.log(`${this.name} nie jest głodny (karmiony mniej niż godzinę temu).`);
59          return false;
60        } else {
61          const healthGain = Math.min(10, hoursSinceLastFed * 2); // Maksymalnie 10 punktów zdrowia
62          this.health += healthGain;
63          this._lastFed = now;
64
65          console.log(`${this.name} został nakarmiony. +${healthGain.toFixed(1)} do zdrowia. Aktualny poziom: ${this.health}%`);
66          return true;
67        }
68      }
69    };
70
71    // Używamy Object.seal, aby zapobiec dodawaniu nowych właściwości
72    Object.seal(dinosaurData);
73
74    // Dodajemy dinozaura do mapy
75    this.dinosaurs.set(id, dinosaurData);
76    console.log(`Dodano dinozaura ${name} (${species}) do systemu monitorowania zdrowia.`);
77    return true;
78  }
79
80  // Pobieranie informacji o dinozaurze
81  getDinosaurInfo(id) {
82    if (!this.dinosaurs.has(id)) {
83      return `Dinozaur o ID ${id} nie istnieje w systemie.`;
84    }
85
86    const dino = this.dinosaurs.get(id);
87    return {
88      id: dino.id,
89      name: dino.name,
90      species: dino.species,
91      health: dino.health,
92      status: dino._status,
93      lastFed: dino._lastFed
94    };
95  }
96
97  // Metoda do symulacji upływu czasu, która obniża zdrowie dinozaurów
98  simulateTimePass(hours) {
99    console.log(`Symulacja upływu ${hours} godzin...`);
100
101    this.dinosaurs.forEach(dino => {
102      // Obniżenie zdrowia - tempo zależy od gatunku
103      let healthReduction = hours;
104
105      if (dino.species.toLowerCase().includes('tyrannosaurus')) {
106        healthReduction *= 1.5; // Tyrannosaurus traci zdrowie szybciej
107      } else if (dino.species.toLowerCase().includes('velociraptor')) {
108        healthReduction *= 1.2; // Velociraptory też dość szybko
109      }
110
111      const newHealth = dino.health - healthReduction;
112      dino.health = newHealth;
113
114      console.log(`${dino.name}: Zdrowie zmniejszone o ${healthReduction.toFixed(1)} do ${dino.health}% (${dino._status})`);
115    });
116
117    this.lastCheckTime = new Date();
118    this.checkAlarms();
119  }
120
121  // Sprawdzanie alarmów zdrowotnych
122  checkAlarms() {
123    console.log("Sprawdzanie alarmów zdrowotnych...");
124
125    const criticalDinos = [];
126    const alarmDinos = [];
127
128    this.dinosaurs.forEach(dino => {
129      if (dino.health <= this.criticalThreshold) {
130        criticalDinos.push(dino);
131      } else if (dino.health <= this.alarmThreshold) {
132        alarmDinos.push(dino);
133      }
134    });
135
136    if (criticalDinos.length > 0) {
137      console.log("=== KRYTYCZNY STAN ZDROWIA ===");
138      criticalDinos.forEach(dino => {
139        console.log(`- ${dino.name} (${dino.species}): ${dino.health}% - WYMAGA NATYCHMIASTOWEJ INTERWENCJI!`);
140      });
141    }
142
143    if (alarmDinos.length > 0) {
144      console.log("=== ALARM ZDROWOTNY ===");
145      alarmDinos.forEach(dino => {
146        console.log(`- ${dino.name} (${dino.species}): ${dino.health}% - Wymaga opieki weterynaryjnej`);
147      });
148    }
149
150    if (criticalDinos.length === 0 && alarmDinos.length === 0) {
151      console.log("Wszystkie dinozaury są w dobrej kondycji zdrowotnej.");
152    }
153  }
154
155  // Generowanie raportu dla wszystkich dinozaurów
156  generateHealthReport() {
157    const now = new Date();
158    let report = `=== RAPORT ZDROWIA DINOZAURÓW (wygenerowany: ${now.toLocaleString()}) ===
159`;
160
161    if (this.dinosaurs.size === 0) {
162      report += "Brak dinozaurów w systemie monitorowania.
163";
164    } else {
165      // Sortowanie dinozaurów według poziomu zdrowia (od najniższego)
166      const sortedDinos = [...this.dinosaurs.values()].sort((a, b) => a.health - b.health);
167
168      sortedDinos.forEach(dino => {
169        report += `${dino.name} (${dino.species})
170`;
171        report += `  Zdrowie: ${dino.health}% (${dino._status})
172`;
173        report += `  Ostatnio karmiony: ${dino._lastFed.toLocaleString()}
174`;
175
176        // Dodajemy rekomendacje
177        if (dino.health < this.criticalThreshold) {
178          report += "  REKOMENDACJA: Natychmiastowa interwencja weterynaryjna! Przeniesienie do ośrodka intensywnej opieki.
179";
180        } else if (dino.health < this.alarmThreshold) {
181          report += "  REKOMENDACJA: Zwiększona częstotliwość karmienia i kontroli weterynaryjnej.
182";
183        }
184
185        report += "
186";
187      });
188    }
189
190    return report;
191  }
192}
193
194// Przyklad użycia
195const healthMonitor = new DinosaurHealthMonitor();
196
197// Dodajemy kilka dinozaurów
198healthMonitor.addDinosaur("TRX-01", "Rex", "Tyrannosaurus Rex");
199healthMonitor.addDinosaur("VEL-01", "Blue", "Velociraptor");
200healthMonitor.addDinosaur("TRI-01", "Trixie", "Triceratops");
201healthMonitor.addDinosaur("BRACH-01", "Bronty", "Brachiosaurus");
202
203// Generujemy raport początkowy
204console.log(healthMonitor.generateHealthReport());
205
206// Symulujemy upływ czasu
207healthMonitor.simulateTimePass(24); // 24 godziny
208
209// Karmimy niektóre dinozaury
210const rex = healthMonitor.dinosaurs.get("TRX-01");
211const blue = healthMonitor.dinosaurs.get("VEL-01");
212rex.feed();
213blue.feed();
214
215// Symulujemy kolejny upływ czasu
216healthMonitor.simulateTimePass(48); // 48 godzin
217
218// Generujemy końcowy raport
219console.log(healthMonitor.generateHealthReport());

Podsumowanie

W tym module poznaliśmy zaawansowane właściwości i metody obiektów JavaScript:

  1. Deskryptory właściwości - pozwalają na precyzyjną kontrolę nad atrybutami właściwości
  2. Gettery i settery - umożliwiają wykonanie kodu podczas odczytu lub zapisu właściwości
  3. Wiązanie this - metody bind, call i apply pomagają w kontrolowaniu kontekstu funkcji
  4. Symbole - zapewniają unikalne identyfikatory dla właściwości obiektów
  5. Zamrażanie obiektów - metody freeze, seal i preventExtensions pozwalają ograniczyć modyfikowalność obiektów
  6. Proxy - pozwala na przechwytywanie i dostosowywanie podstawowych operacji na obiektach

Te techniki są niezbędne przy tworzeniu złożonych systemów, takich jak nasz system monitorowania zdrowia dinozaurów w Parku Jurajskim. Pozwalają na tworzenie bardziej niezawodnych, bezpiecznych i eleganckich rozwiązań programistycznych.

Vai a CodeWorlds