Kurs JavaScript i TypeScript · Moduł 2: Funkcje, tablice i obiekty

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

12 min czytania
W tej lekcji9

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 tej lekcji przyjrzymy się bliżej właściwościom i metodom 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()

Zamraża obiekt (płytko: obiekty zagnieżdżone nadal można zmieniać) - 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

Obiekt Proxy

Obiekt Proxy pozwala 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()}) ===\n`;
159
160    if (this.dinosaurs.size === 0) {
161      report += "Brak dinozaurów w systemie monitorowania.\n";
162    } else {
163      // Sortowanie dinozaurów według poziomu zdrowia (od najniższego)
164      const sortedDinos = [...this.dinosaurs.values()].sort((a, b) => a.health - b.health);
165
166      sortedDinos.forEach(dino => {
167        report += `${dino.name} (${dino.species})\n`;
168        report += `  Zdrowie: ${dino.health}% (${dino._status})\n`;
169        report += `  Ostatnio karmiony: ${dino._lastFed.toLocaleString()}\n`;
170
171        // Dodajemy rekomendacje
172        if (dino.health < this.criticalThreshold) {
173          report += "  REKOMENDACJA: Natychmiastowa interwencja weterynaryjna! Przeniesienie do ośrodka intensywnej opieki.\n";
174        } else if (dino.health < this.alarmThreshold) {
175          report += "  REKOMENDACJA: Zwiększona częstotliwość karmienia i kontroli weterynaryjnej.\n";
176        }
177
178        report += "\n";
179      });
180    }
181
182    return report;
183  }
184}
185
186// Przykład użycia
187const healthMonitor = new DinosaurHealthMonitor();
188
189// Dodajemy kilka dinozaurów
190healthMonitor.addDinosaur("TRX-01", "Rex", "Tyrannosaurus Rex");
191healthMonitor.addDinosaur("VEL-01", "Blue", "Velociraptor");
192healthMonitor.addDinosaur("TRI-01", "Trixie", "Triceratops");
193healthMonitor.addDinosaur("BRACH-01", "Bronty", "Brachiosaurus");
194
195// Generujemy raport początkowy
196console.log(healthMonitor.generateHealthReport());
197
198// Symulujemy upływ czasu
199healthMonitor.simulateTimePass(24); // 24 godziny
200
201// Karmimy niektóre dinozaury
202const rex = healthMonitor.dinosaurs.get("TRX-01");
203const blue = healthMonitor.dinosaurs.get("VEL-01");
204rex.feed();
205blue.feed();
206
207// Symulujemy kolejny upływ czasu
208healthMonitor.simulateTimePass(48); // 48 godzin
209
210// Generujemy końcowy raport
211console.log(healthMonitor.generateHealthReport());

Podsumowanie

W tej lekcji 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.

Kod do tej lekcji: index.js
1// Właściwości i metody obiektów - Park Jurajski
2console.log("Właściwości i Metody Obiektów");
3console.log("Zaawansowane operacje na obiektach\n");
4
5// ===========================================
6// 1. DOSTĘP DO WŁAŚCIWOŚCI
7// ===========================================
8console.log("=== 1. Dostęp do właściwości ===");
9
10const dinosaur = {
11  name: "T-Rex",
12  species: "Tyrannosaurus Rex",
13  age: 8,
14  "sector-id": "A-7", // Właściwość z myślnikiem
15  weight: 8000
16};
17
18// Notacja kropkowa
19console.log("\nNotacja kropkowa:");
20console.log(`   name: ${dinosaur.name}`);
21console.log(`   age: ${dinosaur.age}`);
22
23// Notacja nawiasowa
24console.log("\nNotacja nawiasowa:");
25console.log(`   ['species']: ${dinosaur['species']}`);
26console.log(`   ['sector-id']: ${dinosaur['sector-id']}`); // Wymagane dla myślników
27
28// Dynamiczny dostęp
29const prop = "weight";
30console.log(`\nDynamiczny dostęp [${prop}]: ${dinosaur[prop]}`);
31
32// ===========================================
33// 2. DODAWANIE I MODYFIKOWANIE WŁAŚCIWOŚCI
34// ===========================================
35console.log("\n=== 2. Dodawanie i modyfikowanie ===");
36
37const dino = {
38  name: "Blue",
39  species: "Velociraptor"
40};
41
42console.log("\nPoczątkowo:", dino);
43
44// Dodawanie nowych właściwości
45dino.age = 5;
46dino.weight = 80;
47dino["sector"] = "B";
48
49console.log("Po dodaniu:", dino);
50
51// Modyfikowanie istniejących
52dino.age = 6;
53dino.weight = 85;
54
55console.log("Po modyfikacji:", dino);
56
57// ===========================================
58// 3. USUWANIE WŁAŚCIWOŚCI
59// ===========================================
60console.log("\n=== 3. Usuwanie właściwości ===");
61
62const tempDino = {
63  name: "Charlie",
64  species: "Velociraptor",
65  tempId: 12345,
66  debugMode: true
67};
68
69console.log("\nPrzed usunięciem:", tempDino);
70
71delete tempDino.tempId;
72delete tempDino.debugMode;
73
74console.log("Po usunięciu:", tempDino);
75
76// ===========================================
77// 4. SPRAWDZANIE ISTNIENIA WŁAŚCIWOŚCI
78// ===========================================
79console.log("\n=== 4. Sprawdzanie istnienia ===");
80
81const checkDino = {
82  name: "Rexy",
83  age: 8,
84  sector: null
85};
86
87// Operator 'in'
88console.log("\nOperator 'in':");
89console.log(`   'name' in checkDino: ${'name' in checkDino}`);
90console.log(`   'weight' in checkDino: ${'weight' in checkDino}`);
91console.log(`   'sector' in checkDino: ${'sector' in checkDino}`); // true mimo null!
92
93// hasOwnProperty()
94console.log("\nhasOwnProperty():");
95console.log(`   hasOwnProperty('name'): ${checkDino.hasOwnProperty('name')}`);
96console.log(`   hasOwnProperty('toString'): ${checkDino.hasOwnProperty('toString')}`); // false (odziedziczona)
97
98// undefined check
99console.log("\nSprawdzanie undefined:");
100console.log(`   name !== undefined: ${checkDino.name !== undefined}`);
101console.log(`   weight !== undefined: ${checkDino.weight !== undefined}`);
102
103// ===========================================
104// 5. ITERACJA PO WŁAŚCIWOŚCIACH
105// ===========================================
106console.log("\n=== 5. Iteracja po właściwościach ===");
107
108const iterDino = {
109  name: "Trike",
110  species: "Triceratops",
111  age: 7,
112  weight: 9000,
113  sector: "C"
114};
115
116// for...in
117console.log("\nfor...in:");
118for (const key in iterDino) {
119  console.log(`   ${key}: ${iterDino[key]}`);
120}
121
122// Object.keys()
123console.log("\nObject.keys():");
124const keys = Object.keys(iterDino);
125console.log(`   Klucze: ${keys.join(", ")}`);
126
127// Object.values()
128console.log("\nObject.values():");
129const values = Object.values(iterDino);
130console.log(`   Wartości: ${values.join(", ")}`);
131
132// Object.entries()
133console.log("\nObject.entries():");
134Object.entries(iterDino).forEach(([key, value]) => {
135  console.log(`   ${key} => ${value}`);
136});
137
138// ===========================================
139// 6. METODY OBIEKTÓW
140// ===========================================
141console.log("\n=== 6. Metody obiektów ===");
142
143const methodDino = {
144  name: "Stego",
145  species: "Stegosaurus",
146  health: 85,
147
148  // Metoda tradycyjna
149  getInfo: function() {
150    return `${this.name} (${this.species})`;
151  },
152
153  // Metoda skrócona (ES6)
154  roar() {
155    return `${this.name} says: ROAARRR!`;
156  },
157
158  // Metoda z parametrami
159  updateHealth(change) {
160    this.health += change;
161    if (this.health > 100) this.health = 100;
162    if (this.health < 0) this.health = 0;
163    return `Zdrowie: ${this.health}%`;
164  },
165
166  // Metoda zwracająca obiekt
167  getStats() {
168    return {
169      name: this.name,
170      health: this.health,
171      status: this.health >= 80 ? "Zdrowy" : "Wymaga uwagi"
172    };
173  }
174};
175
176console.log("\n", methodDino.getInfo());
177console.log("   ", methodDino.roar());
178console.log("   ", methodDino.updateHealth(10));
179console.log("   Stats:", methodDino.getStats());
180
181// ===========================================
182// 7. THIS W METODACH
183// ===========================================
184console.log("\n=== 7. Kontekst 'this' w metodach ===");
185
186const thisDino = {
187  name: "Brachio",
188  sector: "D",
189
190  showInfo() {
191    console.log(`\n${this.name} w sektorze ${this.sector}`);
192  },
193
194  // Problem z zagnieżdżoną funkcją
195  showWithTraditional() {
196    console.log(`\n${this.name}:`);
197
198    ["Karmienie", "Badanie", "Czyszczenie"].forEach(function(task) {
199      // this tutaj NIE odnosi się do thisDino!
200      console.log(`   - ${task}`); // Nie możemy użyć this.name
201    });
202  },
203
204  // Rozwiązanie z arrow function
205  showWithArrow() {
206    console.log(`\n${this.name}:`);
207
208    ["Karmienie", "Badanie", "Czyszczenie"].forEach(task => {
209      console.log(`   - ${task} dla ${this.name}`); // this działa!
210    });
211  }
212};
213
214thisDino.showInfo();
215thisDino.showWithTraditional();
216thisDino.showWithArrow();
217
218// ===========================================
219// 8. COMPUTED PROPERTY NAMES
220// ===========================================
221console.log("\n=== 8. Computed property names ===");
222
223const propName = "species";
224const propValue = "Velociraptor";
225const sectorNumber = 7;
226
227const computedDino = {
228  name: "Echo",
229  [propName]: propValue, // Obliczana nazwa właściwości
230  [`sector-${sectorNumber}`]: true,
231  [`health_${Date.now()}`]: 95
232};
233
234console.log("\nObiekt z obliczanymi nazwami:");
235console.log(computedDino);
236
237// ===========================================
238// 9. PROPERTY DESCRIPTORS
239// ===========================================
240console.log("\n=== 9. Property descriptors ===");
241
242const descriptorDino = {};
243
244// Definiowanie właściwości z deskryptorami
245Object.defineProperty(descriptorDino, 'name', {
246  value: "Rex",
247  writable: true,
248  enumerable: true,
249  configurable: true
250});
251
252Object.defineProperty(descriptorDino, 'id', {
253  value: 12345,
254  writable: false, // Tylko do odczytu
255  enumerable: false, // Nie pojawi się w for...in
256  configurable: false // Nie można usunąć ani zmienić deskryptora
257});
258
259console.log("\nWłaściwości:");
260console.log(`   name: ${descriptorDino.name}`);
261console.log(`   id: ${descriptorDino.id}`);
262
263console.log("\nPróba modyfikacji:");
264descriptorDino.name = "Rexy"; // Zadziała
265console.log(`   name po zmianie: ${descriptorDino.name}`);
266
267descriptorDino.id = 99999; // Nie zadziała (writable: false)
268console.log(`   id po próbie zmiany: ${descriptorDino.id}`);
269
270console.log("\nfor...in (id nie pojawi się):");
271for (const key in descriptorDino) {
272  console.log(`   ${key}: ${descriptorDino[key]}`);
273}
274
275// ===========================================
276// 10. PRZYKŁAD KOMPLEKSOWY
277// ===========================================
278console.log("\n=== 10. System zarządzania dinozaurem - przykład kompleksowy ===");
279
280class DinosaurManager {
281  constructor(name, species) {
282    // Właściwości podstawowe
283    this._name = name;
284    this._species = species;
285    this._health = 100;
286    this._fed = false;
287    this._lastFed = null;
288    this._activities = [];
289
290    // Właściwości obliczane
291    Object.defineProperty(this, 'id', {
292      value: `DINO-${Date.now()}`,
293      writable: false,
294      enumerable: true
295    });
296  }
297
298  // Gettery i settery
299  get name() {
300    return this._name;
301  }
302
303  get species() {
304    return this._species;
305  }
306
307  get health() {
308    return this._health;
309  }
310
311  set health(value) {
312    if (value < 0) value = 0;
313    if (value > 100) value = 100;
314
315    const oldHealth = this._health;
316    this._health = value;
317
318    this.logActivity(`Zdrowie zmienione z ${oldHealth}% na ${value}%`);
319  }
320
321  get status() {
322    if (this._health >= 80) return "Doskonały";
323    if (this._health >= 50) return "Dobry";
324    if (this._health >= 20) return "Słaby";
325    return "Krytyczny";
326  }
327
328  get needsFeeding() {
329    if (!this._lastFed) return true;
330    const hoursSinceLastFed = (Date.now() - this._lastFed) / (1000 * 60 * 60);
331    return hoursSinceLastFed > 8;
332  }
333
334  // Metody akcji
335  feed() {
336    this._fed = true;
337    this._lastFed = Date.now();
338    this.health += 5;
339    this.logActivity("Nakarmiony");
340    return `${this._name} został nakarmiony`;
341  }
342
343  heal(amount) {
344    this.health += amount;
345    this.logActivity(`Leczony (+${amount}% zdrowia)`);
346    return `${this._name} wyleczony o ${amount}%`;
347  }
348
349  exercise() {
350    this.health -= 5;
351    this.logActivity("Ćwiczenia");
352    return `${this._name} wykonał ćwiczenia`;
353  }
354
355  // Metody pomocnicze
356  logActivity(activity) {
357    this._activities.push({
358      activity,
359      timestamp: new Date().toISOString(),
360      health: this._health
361    });
362  }
363
364  getActivityLog(limit = 5) {
365    return this._activities.slice(-limit);
366  }
367
368  getInfo() {
369    return {
370      id: this.id,
371      name: this._name,
372      species: this._species,
373      health: this._health,
374      status: this.status,
375      fed: this._fed,
376      needsFeeding: this.needsFeeding,
377      activitiesCount: this._activities.length
378    };
379  }
380
381  displayReport() {
382    console.log(`\n==================================================`);
383    console.log(`RAPORT DINOZAURA: ${this._name}`);
384    console.log("=".repeat(50));
385
386    const info = this.getInfo();
387    console.log(`ID: ${info.id}`);
388    console.log(`Gatunek: ${info.species}`);
389    console.log(`Zdrowie: ${info.health}% (${info.status})`);
390    console.log(`Nakarmiony: ${info.fed ? "Tak" : "Nie"}`);
391    console.log(`Wymaga karmienia: ${info.needsFeeding ? "Tak" : "Nie"}`);
392    console.log(`Liczba aktywności: ${info.activitiesCount}`);
393
394    const recentActivities = this.getActivityLog(5);
395    if (recentActivities.length > 0) {
396      console.log(`\nOstatnie aktywności:`);
397      recentActivities.forEach((act, i) => {
398        const time = new Date(act.timestamp).toLocaleTimeString();
399        console.log(`  ${i + 1}. [${time}] ${act.activity} (zdrowie: ${act.health}%)`);
400      });
401    }
402  }
403
404  // Metoda statyczna
405  static compare(dino1, dino2) {
406    console.log(`\nPorównanie: ${dino1.name} vs ${dino2.name}`);
407    console.log(`   Zdrowie: ${dino1.health}% vs ${dino2.health}%`);
408    console.log(`   Status: ${dino1.status} vs ${dino2.status}`);
409
410    if (dino1.health > dino2.health) {
411      console.log(`   ${dino1.name} jest zdrowszy`);
412    } else if (dino2.health > dino1.health) {
413      console.log(`   ${dino2.name} jest zdrowszy`);
414    } else {
415      console.log(`   Obaj mają takie samo zdrowie`);
416    }
417  }
418}
419
420// Demonstracja systemu
421const rexy = new DinosaurManager("Rexy", "T-Rex");
422const blue = new DinosaurManager("Blue", "Velociraptor");
423
424console.log();
425console.log(rexy.feed());
426console.log(rexy.exercise());
427console.log(rexy.heal(10));
428
429console.log();
430console.log(blue.feed());
431console.log(blue.exercise());
432console.log(blue.exercise());
433
434rexy.displayReport();
435blue.displayReport();
436
437DinosaurManager.compare(rexy, blue);
438
439console.log("\nGratulacje! Opanowałeś właściwości i metody obiektów!");

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Do czego służy Object.defineProperty() w JavaScript?

  2. 2. Do czego służą gettery (get) i settery (set) w obiektach JavaScript?

Zadania praktyczne w grze

  • Klikanie w kolejności

    Ułóż elementy definicji gettera w obiekcie:

Przydatne artykuły