Kurs JavaScript i TypeScript · Moduł 2: Funkcje, tablice i obiekty
Słowo kluczowe this w JavaScript
W tej lekcji9
Witaj ponownie w Parku Jurajskim! Dzisiaj zagłębimy się w jedno z najbardziej intrygujących i czasami mylących zagadnień w JavaScript: słowo kluczowe this. Zrozumienie this jest kluczowe dla efektywnego programowania obiektowego w JavaScript i tworzenia złożonych systemów, takich jak nasz system zarządzania parkiem.
Czym jest this?
Słowo kluczowe this w JavaScript odnosi się do obiektu, w którego kontekście wykonywana jest funkcja. Można o nim myśleć jak o "aktualnym miejscu pracy" - podobnie jak pracownik Parku Jurajskiego może pracować w różnych lokalizacjach (np. laboratorium, zagroda T-Rexa, centrum kontroli), tak samo funkcja może być wykonywana w różnych kontekstach.
Wartość this nie jest ustalana w momencie definiowania funkcji, ale w momencie jej wywołania, co może powodować pewne zamieszanie. Wartość this zależy od tego, jak funkcja jest wywoływana:
- W kontekście globalnym
- Jako metoda obiektu
- Jako konstruktor
- Za pomocą metod
call(),apply()lubbind() - W funkcjach strzałkowych
Przyjrzyjmy się każdemu z tych przypadków.
1. This w kontekście globalnym
Gdy this jest używane w zwykłej funkcji (niewywołanej jako metoda obiektu, poza trybem ścisłym), odnosi się do obiektu globalnego: w przeglądarce window, a w Node.js global (wspólna nazwa obu to globalThis). Na najwyższym poziomie skryptu w przeglądarce this to też window, ale w pliku Node.js to module.exports, czyli pusty obiekt {}, a w modułach ES - undefined.
1// W globalnym zasięgu
2console.log(this); // window w przeglądarce, {} (module.exports) w pliku Node.js
3
4// W zwykłej funkcji
5function checkGlobalThis() {
6 console.log(this); // również obiekt window/global
7}
8
9checkGlobalThis();W trybie ścisłym ("use strict";), this w zwykłej funkcji będzie undefined zamiast obiektu globalnego, co pomaga uniknąć przypadkowego tworzenia globalnych zmiennych.
1"use strict";
2
3function strictThis() {
4 console.log(this); // undefined
5}
6
7strictThis();2. This jako metoda obiektu
Gdy funkcja jest wywoływana jako metoda obiektu, this odnosi się do tego obiektu. Jest to jeden z najczęstszych i najbardziej intuicyjnych sposobów użycia this w JavaScript.
1// Obiekt reprezentujący dinozaura
2const velociraptor = {
3 name: "Blue",
4 species: "Velociraptor",
5 age: 5,
6
7 // Metoda wykorzystująca this
8 describe: function() {
9 console.log(`Ten ${this.species} ma na imię ${this.name} i ma ${this.age} lat.`);
10 },
11
12 // Metoda zmieniająca dane dinozaura
13 ageDinosaur: function(years) {
14 this.age += years;
15 console.log(`${this.name} ma teraz ${this.age} lat.`);
16 }
17};
18
19// Wywołanie metod
20velociraptor.describe(); // "Ten Velociraptor ma na imię Blue i ma 5 lat."
21velociraptor.ageDinosaur(2); // "Blue ma teraz 7 lat."W powyższym przykładzie, this wewnątrz metod describe i ageDinosaur odnosi się do obiektu velociraptor, dzięki czemu metody mają dostęp do jego właściwości.
Potencjalna pułapka: utrata kontekstu
Jednak trzeba uważać - kontekst this może zostać utracony, jeśli metodę przypiszemy do innej zmiennej i wywołamy ją jako zwykłą funkcję:
1const velociraptor = {
2 name: "Blue",
3 describe: function() {
4 console.log(`Cześć, jestem ${this.name}!`);
5 }
6};
7
8// Wywołanie jako metoda - this odnosi się do velociraptor
9velociraptor.describe(); // "Cześć, jestem Blue!"
10
11// Ale gdy przypiszemy metodę do zmiennej...
12const describeDino = velociraptor.describe;
13
14// ...i wywołamy jako zwykłą funkcję
15describeDino(); // "Cześć, jestem undefined!"
16// (lub "Cześć, jestem !", jeśli istnieje globalna zmienna name)To częsty problem przy przekazywaniu metod jako funkcji zwrotnych (callbacks):
1const trex = {
2 name: "Rex",
3 roar: function() {
4 console.log(`${this.name} ryczy głośno!`);
5 }
6};
7
8// Bezpośrednie wywołanie działa
9trex.roar(); // "Rex ryczy głośno!"
10
11// Ale jako callback po 1 sekundzie...
12setTimeout(trex.roar, 1000); // this nie wskazuje na trex, więc imię Rex się nie pojawi
13
14// Rozwiązanie (jednym ze sposobów) jest użycie funkcji strzałkowej lub bind():
15setTimeout(() => trex.roar(), 1000); // "Rex ryczy głośno!"
16setTimeout(trex.roar.bind(trex), 1000); // "Rex ryczy głośno!"3. This w konstruktorach
Gdy funkcja jest wywoływana z operatorem new (jako konstruktor), this odnosi się do nowo tworzonego obiektu. Jest to fundamentalny mechanizm tworzenia instancji obiektów w JavaScript.
1// Funkcja konstruktora dla dinozaurów
2function Dinosaur(name, species, age) {
3 // 'this' odnosi się do nowo tworzonego obiektu
4 this.name = name;
5 this.species = species;
6 this.age = age;
7
8 this.describe = function() {
9 console.log(`${this.name} to ${this.species} w wieku ${this.age} lat.`);
10 };
11}
12
13// Tworzenie nowych instancji
14const blue = new Dinosaur("Blue", "Velociraptor", 5);
15const rex = new Dinosaur("Rex", "Tyrannosaurus", 8);
16
17blue.describe(); // "Blue to Velociraptor w wieku 5 lat."
18rex.describe(); // "Rex to Tyrannosaurus w wieku 8 lat."
19
20// Zmodyfikujmy wiek Blue
21blue.age = 6;
22blue.describe(); // "Blue to Velociraptor w wieku 6 lat."W nowoczesnym JavaScript (ES6+) zamiast funkcji konstruktora często używamy klas, które zapewniają bardziej przejrzystą składnię do robienia tego samego:
1// Klasa ES6 dla dinozaurów
2class Dinosaur {
3 constructor(name, species, age) {
4 this.name = name;
5 this.species = species;
6 this.age = age;
7 }
8
9 // Metody są automatycznie dodawane do prototypu
10 describe() {
11 console.log(`${this.name} to ${this.species} w wieku ${this.age} lat.`);
12 }
13
14 // Metoda zmieniająca wiek
15 growOlder(years = 1) {
16 this.age += years;
17 console.log(`${this.name} ma teraz ${this.age} lat.`);
18 }
19}
20
21// Tworzenie instancji
22const trixie = new Dinosaur("Trixie", "Triceratops", 7);
23trixie.describe(); // "Trixie to Triceratops w wieku 7 lat."
24trixie.growOlder(3); // "Trixie ma teraz 10 lat."4. Kontrolowanie this: call(), apply() i bind()
JavaScript oferuje trzy metody, które pozwalają jawnie kontrolować wartość this podczas wywoływania funkcji:
call()
Metoda call() wywołuje funkcję z podaną wartością this i argumentami przekazanymi pojedynczo:
1function examineSpecimen(testType, labNumber) {
2 console.log(`Badanie ${this.species} o imieniu ${this.name}...`);
3 console.log(`Typ testu: ${testType}, Laboratorium: ${labNumber}`);
4}
5
6const specimen = {
7 name: "Echo",
8 species: "Velociraptor",
9 age: 3
10};
11
12// Wywołanie funkcji z 'this' ustawionym na specimen
13examineSpecimen.call(specimen, "DNA", "Lab-A");
14// "Badanie Velociraptor o imieniu Echo..."
15// "Typ testu: DNA, Laboratorium: Lab-A"apply()
Metoda apply() działa podobnie do call(), ale argumenty są przekazywane jako tablica:
1// Ta sama funkcja
2function examineSpecimen(testType, labNumber) {
3 console.log(`Badanie ${this.species} o imieniu ${this.name}...`);
4 console.log(`Typ testu: ${testType}, Laboratorium: ${labNumber}`);
5}
6
7// Wywołanie z argumentami w tablicy
8examineSpecimen.apply(specimen, ["Behawioralny", "Lab-B"]);
9// "Badanie Velociraptor o imieniu Echo..."
10// "Typ testu: Behawioralny, Laboratorium: Lab-B"bind()
Metoda bind() tworzy nową funkcję z trwale przypisaną wartością this:
1const dinosaur = {
2 name: "Delta",
3 species: "Velociraptor"
4};
5
6function makeSound(sound) {
7 console.log(`${this.name} wydaje dźwięk: ${sound}!`);
8}
9
10// Tworzenie nowej funkcji z 'this' przypisanym do dinosaur
11const dinosaurSound = makeSound.bind(dinosaur);
12
13// Teraz możemy wywoływać tę funkcję wielokrotnie
14dinosaurSound("Warczenie"); // "Delta wydaje dźwięk: Warczenie!"
15dinosaurSound("Syczenie"); // "Delta wydaje dźwięk: Syczenie!"
16
17// Możemy też powiązać argumenty
18const deltaRoar = makeSound.bind(dinosaur, "ROAR");
19deltaRoar(); // "Delta wydaje dźwięk: ROAR!"Praktyczny przykład: Tworzenie systemu śledzenia dinozaurów
Poniższy przykład pokazuje, jak można wykorzystać różne metody kontrolowania this do tworzenia elastycznego systemu monitorowania:
1const dinoMonitoringSystem = {
2 activeDinosaurs: [],
3
4 // Rejestracja nowego dinozaura
5 registerDinosaur: function(dinosaur) {
6 this.activeDinosaurs.push(dinosaur);
7 console.log(`Zarejestrowano nowego dinozaura: ${dinosaur.name} (${dinosaur.species})`);
8 },
9
10 // Wyszukiwanie dinozaura po imieniu
11 findDinosaurByName: function(name) {
12 return this.activeDinosaurs.find(dino => dino.name === name);
13 },
14
15 // Uruchomienie procedury alertu
16 triggerAlert: function(message) {
17 console.log(`ALERT SYSTEMOWY: ${message}`);
18
19 // Powiadamianie o wszystkich aktywnych dinozaurach
20 this.activeDinosaurs.forEach(function(dino) {
21 console.log(`- ${dino.name} (${dino.species}) w sektorze ${dino.sector}`);
22 });
23 }
24};
25
26// Klasa Dinosaur z metodą przemieszczania
27class Dinosaur {
28 constructor(name, species, sector) {
29 this.name = name;
30 this.species = species;
31 this.sector = sector;
32 this.status = "spokojny";
33 }
34
35 move(newSector, monitoringSystem) {
36 // Możemy przekazać monitoringSystem jako parametr
37 console.log(`${this.name} przemieszcza się z sektora ${this.sector} do sektora ${newSector}`);
38 this.sector = newSector;
39
40 // Lub użyć call() do wywołania metody z właściwym kontekstem
41 const alertMessage = `Dinozaur ${this.name} wszedł do sektora ${newSector}!`;
42 monitoringSystem.triggerAlert.call(monitoringSystem, alertMessage);
43 }
44
45 // Metoda zmieniająca status dinozaura
46 updateStatus(newStatus) {
47 this.status = newStatus;
48 console.log(`Status dinozaura ${this.name} zmieniony na: ${this.status}`);
49 return this;
50 }
51}
52
53// Inicjalizacja systemu i dinozaurów
54const rex = new Dinosaur("Rex", "Tyrannosaurus", "A");
55const blue = new Dinosaur("Blue", "Velociraptor", "B");
56const delta = new Dinosaur("Delta", "Velociraptor", "B");
57
58// Rejestracja dinozaurów w systemie
59dinoMonitoringSystem.registerDinosaur(rex);
60dinoMonitoringSystem.registerDinosaur(blue);
61dinoMonitoringSystem.registerDinosaur(delta);
62
63// Przemieszczenie dinozaura
64blue.move("C", dinoMonitoringSystem);
65
66// Łańcuchowanie metod (dzięki return this)
67rex.updateStatus("pobudzony").updateStatus("agresywny");
68
69// Wykorzystanie bind() do tworzenia funkcji z predefiniowanym 'this'
70const checkRexStatus = function() {
71 console.log(`Status T-Rexa: ${this.status}`);
72};
73
74const boundCheckStatus = checkRexStatus.bind(rex);
75boundCheckStatus(); // "Status T-Rexa: agresywny"5. This w funkcjach strzałkowych
Funkcje strzałkowe, wprowadzone w ES6, obsługują this inaczej niż tradycyjne funkcje. Nie mają własnego this, zamiast tego "dziedziczą" this z otaczającego zakresu leksykalnego.
1const parkSystem = {
2 name: "Jurassic Park",
3 dinosaurs: ["T-Rex", "Velociraptor", "Triceratops"],
4
5 // Metoda wykorzystująca funkcję strzałkową
6 listDinosaurs: function() {
7 console.log(`${this.name} zawiera następujące dinozaury:`);
8
9 // Funkcja strzałkowa zachowuje 'this' z zakresu listDinosaurs
10 this.dinosaurs.forEach(dino => {
11 console.log(`- ${dino} w parku ${this.name}`);
12 });
13
14 // Dla porównania - tradycyjna funkcja straciłaby kontekst 'this'
15 /*
16 this.dinosaurs.forEach(function(dino) {
17 // 'this' odnosi się do globalnego obiektu lub jest undefined w trybie ścisłym
18 console.log(`- ${dino} w parku ${this.name}`); // this.name będzie undefined
19 });
20 */
21 }
22};
23
24parkSystem.listDinosaurs();
25// "Jurassic Park zawiera następujące dinozaury:"
26// "- T-Rex w parku Jurassic Park"
27// "- Velociraptor w parku Jurassic Park"
28// "- Triceratops w parku Jurassic Park"Funkcje strzałkowe są szczególnie przydatne w przypadkach, gdy chcemy zachować kontekst this w funkcjach zagnieżdżonych, funkcjach zwrotnych (callbacks) lub programowaniu asynchronicznym.
Praktyczny przykład: System monitorowania z czasomierzami
1class DinosaurMonitor {
2 constructor(name, species) {
3 this.name = name;
4 this.species = species;
5 this.status = "Normalny";
6 this.lastUpdated = new Date();
7 this.checkInterval = null;
8 }
9
10 // Rozpoczęcie monitorowania z regularnym sprawdzaniem statusu
11 startMonitoring() {
12 console.log(`Rozpoczęcie monitorowania dinozaura: ${this.name}`);
13
14 // Funkcja strzałkowa zachowuje kontekst 'this'
15 this.checkInterval = setInterval(() => {
16 this.checkStatus();
17 }, 5000);
18
19 // Gdybyśmy użyli zwykłej funkcji, stracilibyśmy kontekst:
20 /*
21 this.checkInterval = setInterval(function() {
22 // 'this' odnosi się tutaj do globalnego obiektu, nie do instancji DinosaurMonitor
23 this.checkStatus(); // Błąd: this.checkStatus is not a function
24 }, 5000);
25 */
26 }
27
28 // Sprawdzenie statusu dinozaura
29 checkStatus() {
30 const now = new Date();
31 const timeSinceLastUpdate = (now - this.lastUpdated) / 1000; // w sekundach
32
33 console.log(`Sprawdzanie statusu dinozaura ${this.name} (${this.species})...`);
34 console.log(`Ostatnia aktualizacja: ${timeSinceLastUpdate.toFixed(1)} sekund temu`);
35 console.log(`Status: ${this.status}`);
36
37 this.lastUpdated = now;
38
39 // Symulacja zmiany statusu (w rzeczywistym systemie byłyby tu dane z czujników)
40 if (Math.random() < 0.3) {
41 const possibleStatuses = ["Normalny", "Pobudzony", "Odpoczywający", "Żerujący", "Agresywny"];
42 this.status = possibleStatuses[Math.floor(Math.random() * possibleStatuses.length)];
43 console.log(`Nowy status: ${this.status}`);
44 }
45 }
46
47 // Zatrzymanie monitorowania
48 stopMonitoring() {
49 if (this.checkInterval) {
50 clearInterval(this.checkInterval);
51 this.checkInterval = null;
52 console.log(`Zatrzymano monitorowanie dinozaura: ${this.name}`);
53 }
54 }
55}
56
57// Utworzenie i uruchomienie monitora
58const blueMonitor = new DinosaurMonitor("Blue", "Velociraptor");
59blueMonitor.startMonitoring();
60
61// Zatrzymanie monitora po 20 sekundach
62setTimeout(() => {
63 blueMonitor.stopMonitoring();
64}, 20000);Typowe pułapki związane z this
Słowo kluczowe this może być źródłem wielu błędów w JavaScript. Oto kilka typowych pułapek i jak ich unikać:
1. Utrata kontekstu w funkcjach zagnieżdżonych
1const dinosaur = {
2 name: "Rex",
3 behaviors: ["polowanie", "odpoczynek", "bieganie"],
4
5 logBehaviors: function() {
6 // 'this' odnosi się tutaj do 'dinosaur'
7 console.log(`Dinozaur ${this.name} wykazuje następujące zachowania:`);
8
9 // Problem: 'this' w funkcji forEach odnosi się do globalnego obiektu
10 this.behaviors.forEach(function(behavior) {
11 // 'this.name' jest undefined
12 console.log(`- ${this.name} lubi ${behavior}`);
13 });
14 }
15};
16
17dinosaur.logBehaviors();
18// "Dinozaur Rex wykazuje następujące zachowania:"
19// "- undefined lubi polowanie"
20// "- undefined lubi odpoczynek"
21// "- undefined lubi bieganie"Rozwiązania:
- Użyj funkcji strzałkowej:
1const dinosaur = {
2 name: "Rex",
3 behaviors: ["polowanie", "odpoczynek", "bieganie"],
4
5 logBehaviors: function() {
6 console.log(`Dinozaur ${this.name} wykazuje następujące zachowania:`);
7
8 // Funkcja strzałkowa zachowuje 'this' z otaczającego zakresu
9 this.behaviors.forEach(behavior => {
10 console.log(`- ${this.name} lubi ${behavior}`);
11 });
12 }
13};- Zachowaj referencję do
thisw zmiennej:
1const dinosaur = {
2 name: "Rex",
3 behaviors: ["polowanie", "odpoczynek", "bieganie"],
4
5 logBehaviors: function() {
6 console.log(`Dinozaur ${this.name} wykazuje następujące zachowania:`);
7
8 // Zachowanie referencji do 'this' w zmiennej 'self' lub 'that'
9 const self = this;
10 this.behaviors.forEach(function(behavior) {
11 console.log(`- ${self.name} lubi ${behavior}`);
12 });
13 }
14};- Użyj metody
bind():
1const dinosaur = {
2 name: "Rex",
3 behaviors: ["polowanie", "odpoczynek", "bieganie"],
4
5 logBehaviors: function() {
6 console.log(`Dinozaur ${this.name} wykazuje następujące zachowania:`);
7
8 // Użycie bind() do powiązania 'this'
9 this.behaviors.forEach(function(behavior) {
10 console.log(`- ${this.name} lubi ${behavior}`);
11 }.bind(this));
12 }
13};2. Metody jako callbacks
Gdy metoda obiektu jest przekazywana jako callback, traci swój kontekst this:
1class DinosaurTracker {
2 constructor(name) {
3 this.name = name;
4 this.tracked = false;
5 }
6
7 startTracking() {
8 this.tracked = true;
9 console.log(`Rozpoczęto śledzenie dinozaura ${this.name}`);
10 }
11}
12
13const blueTracker = new DinosaurTracker("Blue");
14
15// Działa poprawnie
16blueTracker.startTracking();
17
18// Nie działa - 'this' nie odnosi się do blueTracker
19setTimeout(blueTracker.startTracking, 1000); // this nie wskazuje na blueTrackerRozwiązania:
- Użyj funkcji strzałkowej:
1setTimeout(() => blueTracker.startTracking(), 1000);- Użyj
bind():
1setTimeout(blueTracker.startTracking.bind(blueTracker), 1000);Zaawansowany przykład: System zarządzania parkiem
Poniżej znajduje się bardziej złożony przykład systemu zarządzania Parkiem Jurajskim, który wykorzystuje różne aspekty słowa kluczowego this:
1// Główny system zarządzania parkiem
2const parkManagementSystem = {
3 parkName: "Jurassic Park",
4 status: "operational",
5 securityLevel: "normal",
6
7 // Podsystemy parku
8 subsystems: {
9 power: { status: "online", output: "100%" },
10 security: { status: "active", fences: "operational" },
11 visitorCenter: { status: "open", visitors: 120 }
12 },
13
14 // Metoda inicjująca system
15 initialize: function() {
16 console.log(`Inicjalizacja systemu zarządzania parkiem: ${this.parkName}`);
17
18 // Sprawdzenie wszystkich podsystemów
19 this.checkAllSubsystems();
20
21 // Ustawienie regularnego monitorowania
22 this.startMonitoring();
23
24 return this;
25 },
26
27 // Sprawdzenie stanu podsystemów
28 checkAllSubsystems: function() {
29 console.log("Sprawdzanie statusu wszystkich podsystemów...");
30
31 // Użycie Object.entries i funkcji strzałkowej dla zachowania kontekstu 'this'
32 Object.entries(this.subsystems).forEach(([name, system]) => {
33 console.log(`- Podsystem ${name}: ${system.status}`);
34
35 // Wywołanie specyficznych sprawdzeń dla każdego podsystemu
36 this[`check${name.charAt(0).toUpperCase() + name.slice(1)}`]();
37 });
38 },
39
40 // Specyficzne metody sprawdzające dla każdego podsystemu
41 checkPower: function() {
42 const powerSystem = this.subsystems.power;
43 console.log(` Sprawdzanie zasilania: ${powerSystem.status}, Moc: ${powerSystem.output}`);
44 },
45
46 checkSecurity: function() {
47 const securitySystem = this.subsystems.security;
48 console.log(` Sprawdzanie zabezpieczeń: ${securitySystem.status}, Ogrodzenia: ${securitySystem.fences}`);
49 },
50
51 checkVisitorCenter: function() {
52 const visitorSystem = this.subsystems.visitorCenter;
53 console.log(` Sprawdzanie centrum dla zwiedzających: ${visitorSystem.status}, Odwiedzający: ${visitorSystem.visitors}`);
54 },
55
56 // Rozpoczęcie regularnego monitorowania
57 startMonitoring: function() {
58 console.log("Rozpoczęcie regularnego monitorowania parku...");
59
60 // Funkcja strzałkowa dla zachowania kontekstu 'this'
61 this.monitoringInterval = setInterval(() => {
62 this.checkSecurityStatus();
63 }, 10000);
64
65 // Symulacja wywołania metody po 5 sekundach
66 setTimeout(() => this.simulateSecurityAlert(), 5000);
67 },
68
69 // Sprawdzenie statusu bezpieczeństwa
70 checkSecurityStatus: function() {
71 console.log(`[MONITOR] Status parku: ${this.status}, Poziom bezpieczeństwa: ${this.securityLevel}`);
72 },
73
74 // Symulacja alertu bezpieczeństwa
75 simulateSecurityAlert: function() {
76 console.log("SYMULACJA: Wykryto naruszenie zabezpieczeń w sektorze B!");
77
78 // Zmiana statusu parku i poziomu bezpieczeństwa
79 this.status = "alert";
80 this.securityLevel = "high";
81 console.log(`Status parku zmieniony na: ${this.status}`);
82 console.log(`Poziom bezpieczeństwa podniesiony do: ${this.securityLevel}`);
83
84 // Bezpośrednie sprawdzenie statusu
85 this.checkSecurityStatus();
86
87 // Utworzenie zespołu reagowania kryzysowego używając konstruktora
88 const responseTeam = new EmergencyResponseTeam(this);
89
90 // Przekazanie metody jako callback z zachowaniem kontekstu
91 responseTeam.respondToAlert(this.resolveSecurityAlert.bind(this));
92 },
93
94 // Rozwiązanie alertu bezpieczeństwa
95 resolveSecurityAlert: function(report) {
96 console.log(`Otrzymano raport od zespołu reagowania: ${report}`);
97
98 // Przywrócenie normalnego statusu
99 this.status = "operational";
100 this.securityLevel = "normal";
101 console.log("Alert bezpieczeństwa rozwiązany.");
102 console.log(`Status parku przywrócony do: ${this.status}`);
103
104 // Sprawdzenie aktualnego statusu
105 this.checkSecurityStatus();
106 },
107
108 // Zatrzymanie monitorowania
109 stopMonitoring: function() {
110 if (this.monitoringInterval) {
111 clearInterval(this.monitoringInterval);
112 console.log("Monitorowanie parku zatrzymane.");
113 }
114 }
115};
116
117// Konstruktor dla zespołu reagowania kryzysowego
118function EmergencyResponseTeam(park) {
119 // 'this' odnosi się do nowego obiektu
120 this.teamName = "Zespół Reagowania Kryzysowego";
121 this.members = ["Owen Grady", "Claire Dearing", "Dr. Wu"];
122 this.assignedPark = park;
123
124 this.respondToAlert = function(callback) {
125 console.log(`${this.teamName} reaguje na alert w parku: ${this.assignedPark.parkName}`);
126
127 // Symulacja czasu reakcji
128 setTimeout(() => {
129 console.log(`${this.teamName} zabezpieczył obszar i przywrócił ogrodzenia.`);
130
131 // Wywołanie callbacka z raportem
132 if (typeof callback === 'function') {
133 callback("Zagrożenie zneutralizowane, ogrodzenia naprawione");
134 }
135 }, 3000);
136 };
137}
138
139// Inicjalizacja i uruchomienie systemu
140parkManagementSystem.initialize();
141
142// Zatrzymanie systemu po 20 sekundach
143setTimeout(() => {
144 parkManagementSystem.stopMonitoring();
145}, 20000);Podsumowanie i najlepsze praktyki
Słowo kluczowe this jest potężnym narzędziem w JavaScript, ale wymaga zrozumienia, jak się zachowuje w różnych kontekstach:
W globalnym kontekście:
thistowindoww przeglądarce imodule.exports({}) w pliku Node.js; w zwykłej funkcji poza trybem ścisłym - obiekt globalny (globalThis).W metodzie obiektu:
thisodnosi się do obiektu, który wywołał metodę.W konstruktorze:
thisodnosi się do nowo tworzonego obiektu.W funkcjach strzałkowych:
thisjest dziedziczone z otaczającego zakresu leksykalnego.Przy użyciu call/apply/bind:
thisjest jawnie określone przez programistę.
Najlepsze praktyki:
Używaj funkcji strzałkowych dla funkcji zwrotnych (callbacks) i funkcji zagnieżdżonych, aby zachować kontekst
this.Używaj metod
call(),apply()lubbind(), gdy potrzebujesz jawnie kontrolować wartośćthis.Unikaj tworzenia metod w konstruktorach, zamiast tego dodawaj je do prototypu, aby uniknąć duplikacji kodu.
Pamiętaj, że
thisjest ustalane podczas wywoływania funkcji, a nie podczas jej definiowania.Używaj klas ES6 zamiast tradycyjnych konstruktorów, aby uzyskać bardziej przejrzysty kod.
W następnej lekcji zajmiemy się głębiej tablicami w JavaScript, które są kluczowym elementem zarządzania kolekcjami danych w naszym Parku Jurajskim.
Kod do tej lekcji: index.js
1// Słowo kluczowe 'this' - Park Jurajski
2console.log("Laboratorium 'this' w JavaScript");
3console.log("Zrozumienie kontekstu wykonania\n");
4
5// ===========================================
6// 1. PODSTAWY 'this'
7// ===========================================
8console.log("=== 1. Czym jest 'this'? ===");
9
10// 'this' odnosi się do obiektu, w kontekście którego wykonuje się kod
11
12const dinosaur = {
13 name: "Tyrannosaurus Rex",
14 age: 8,
15
16 roar: function() {
17 console.log(`\n${this.name} ryczy: ROAARRR!`);
18 console.log(` Wiek: ${this.age} lat`);
19 }
20};
21
22dinosaur.roar(); // 'this' wskazuje na obiekt 'dinosaur'
23
24// ===========================================
25// 2. 'this' W RÓŻNYCH KONTEKSTACH
26// ===========================================
27console.log("\n=== 2. Konteksty 'this' ===");
28
29// Kontekst 1: Metoda obiektu
30const raptor = {
31 name: "Velociraptor",
32 speed: 40,
33
34 run: function() {
35 console.log(`\n${this.name} biegnący z prędkością ${this.speed} km/h`);
36 }
37};
38
39raptor.run(); // this = raptor
40
41// Kontekst 2: Funkcja globalna (strict mode)
42function globalFunction() {
43 console.log("\nGlobalna funkcja - this:", typeof this);
44 // W strict mode: undefined
45 // Bez strict mode: window (przeglądarka) lub global (Node.js)
46}
47
48globalFunction();
49
50// Kontekst 3: Konstruktor
51function Dinosaur(name, species) {
52 this.name = name;
53 this.species = species;
54 this.roar = function() {
55 console.log(`${this.name} (${this.species}): ROAARRR!`);
56 };
57}
58
59const trex = new Dinosaur("Rexy", "T-Rex");
60console.log("\nKonstruktor:");
61console.log(` Imię: ${trex.name}`);
62console.log(` Gatunek: ${trex.species}`);
63trex.roar();
64
65// ===========================================
66// 3. UTRATA KONTEKSTU 'this'
67// ===========================================
68console.log("\n=== 3. Utrata kontekstu 'this' ===");
69
70const park = {
71 name: "Jurassic Park",
72 dinosaurs: ["T-Rex", "Velociraptor", "Triceratops"],
73
74 displayDinosaurs: function() {
75 console.log(`\nDinozaury w ${this.name}:`);
76
77 // PROBLEM: forEach z tradycyjną funkcją
78 // this.dinosaurs.forEach(function(dino) {
79 // console.log(` - ${dino} z ${this.name}`); // this.name = undefined!
80 // });
81
82 // ROZWIĄZANIE 1: Arrow function
83 this.dinosaurs.forEach(dino => {
84 console.log(` - ${dino} z ${this.name}`);
85 });
86 }
87};
88
89park.displayDinosaurs();
90
91// ROZWIĄZANIE 2: bind()
92const park2 = {
93 name: "Isla Nublar",
94 dinosaurs: ["Stegosaurus", "Brachiosaurus"],
95
96 displayDinosaurs: function() {
97 console.log(`\nDinozaury w ${this.name}:`);
98
99 this.dinosaurs.forEach(function(dino) {
100 console.log(` - ${dino} z ${this.name}`);
101 }.bind(this)); // Przywiązanie this
102 }
103};
104
105park2.displayDinosaurs();
106
107// ===========================================
108// 4. METODY: call(), apply(), bind()
109// ===========================================
110console.log("\n=== 4. Metody: call(), apply(), bind() ===");
111
112const dinoInfo = {
113 describe: function(habitat, diet) {
114 console.log(`\n${this.name}:`);
115 console.log(` Środowisko: ${habitat}`);
116 console.log(` Dieta: ${diet}`);
117 }
118};
119
120const dino1 = { name: "T-Rex" };
121const dino2 = { name: "Triceratops" };
122
123// call() - wywołanie z jawnym this i argumentami
124dinoInfo.describe.call(dino1, "Las tropikalny", "Mięsożerca");
125
126// apply() - jak call(), ale argumenty w tablicy
127dinoInfo.describe.apply(dino2, ["Sawanna", "Roślinożerca"]);
128
129// bind() - tworzy nową funkcję z przywiązanym this
130const describeTRex = dinoInfo.describe.bind(dino1);
131describeTRex("Dżungla", "Mięsożerca");
132
133// ===========================================
134// 5. ARROW FUNCTIONS I 'this'
135// ===========================================
136console.log("\n=== 5. Arrow functions i 'this' ===");
137
138const enclosure = {
139 id: "A-7",
140 dinosaur: "Velociraptor",
141
142 // Tradycyjna metoda
143 checkTraditional: function() {
144 console.log(`\nWybieg ${this.id} (tradycyjna):`);
145
146 setTimeout(function() {
147 // console.log(` Dinozaur: ${this.dinosaur}`); // undefined! this się zmieniło
148 console.log(" this.dinosaur = undefined (utrata kontekstu)");
149 }, 100);
150 },
151
152 // Rozwiązanie z arrow function
153 checkArrow: function() {
154 console.log(`\nWybieg ${this.id} (arrow):`);
155
156 setTimeout(() => {
157 console.log(` Dinozaur: ${this.dinosaur}`); // Działa! Arrow zachowuje this
158 }, 100);
159 }
160};
161
162enclosure.checkTraditional();
163
164setTimeout(() => {
165 enclosure.checkArrow();
166}, 150);
167
168// ===========================================
169// 6. 'this' W KONSTRUKTORACH I KLASACH
170// ===========================================
171console.log("\n=== 6. 'this' w konstruktorach i klasach ===");
172
173// Konstruktor funkcyjny
174function DinosaurPark(name) {
175 this.name = name;
176 this.dinosaurs = [];
177
178 this.addDinosaur = function(dino) {
179 this.dinosaurs.push(dino);
180 console.log(`Dodano ${dino} do ${this.name}`);
181 };
182
183 this.getCount = function() {
184 return this.dinosaurs.length;
185 };
186}
187
188const park1 = new DinosaurPark("Park Jurajski");
189park1.addDinosaur("T-Rex");
190park1.addDinosaur("Velociraptor");
191console.log(`Łącznie dinozaurów: ${park1.getCount()}`);
192
193// Klasa ES6
194class ModernPark {
195 constructor(name) {
196 this.name = name;
197 this.dinosaurs = [];
198 }
199
200 addDinosaur(dino) {
201 this.dinosaurs.push(dino);
202 console.log(`\nDodano ${dino} do ${this.name}`);
203 }
204
205 getCount() {
206 return this.dinosaurs.length;
207 }
208
209 displayAll() {
210 console.log(`\n${this.name} - Lista dinozaurów:`);
211 this.dinosaurs.forEach((dino, i) => {
212 console.log(` ${i + 1}. ${dino}`);
213 });
214 }
215}
216
217const park3 = new ModernPark("Isla Nublar");
218park3.addDinosaur("Triceratops");
219park3.addDinosaur("Stegosaurus");
220park3.addDinosaur("Brachiosaurus");
221park3.displayAll();
222console.log(`Łącznie: ${park3.getCount()}`);
223
224// ===========================================
225// 7. PROBLEM: METODA JAKO CALLBACK
226// ===========================================
227console.log("\n=== 7. Problem: metoda jako callback ===");
228
229const securitySystem = {
230 level: "HIGH",
231
232 checkSecurity: function() {
233 console.log(`Poziom zabezpieczeń: ${this.level}`);
234 }
235};
236
237// PROBLEM: this zostanie utracony
238setTimeout(securitySystem.checkSecurity, 100); // this.level = undefined
239
240// ROZWIĄZANIE 1: Arrow function wrapper
241setTimeout(() => {
242 securitySystem.checkSecurity();
243}, 150);
244
245// ROZWIĄZANIE 2: bind()
246setTimeout(securitySystem.checkSecurity.bind(securitySystem), 200);
247
248// ===========================================
249// 8. PRZYKŁAD KOMPLEKSOWY
250// ===========================================
251console.log("\n=== 8. System zarządzania parkiem - przykład kompleksowy ===");
252
253class JurassicParkManagement {
254 constructor(parkName) {
255 this.parkName = parkName;
256 this.dinosaurs = [];
257 this.visitors = 0;
258 this.status = "CLOSED";
259 }
260
261 openPark() {
262 this.status = "OPEN";
263 console.log(`\n${this.parkName} - Park otwarty`);
264 }
265
266 closePark() {
267 this.status = "CLOSED";
268 console.log(`\n${this.parkName} - Park zamknięty`);
269 }
270
271 addDinosaur(name, species, sector) {
272 const dino = { name, species, sector, health: 100 };
273 this.dinosaurs.push(dino);
274 console.log(`Dodano ${name} (${species}) do sektora ${sector}`);
275 }
276
277 addVisitors(count) {
278 this.visitors += count;
279 console.log(`Dodano ${count} odwiedzających. Łącznie: ${this.visitors}`);
280 }
281
282 checkDinosaur(name) {
283 const dino = this.dinosaurs.find(d => d.name === name);
284 if (dino) {
285 console.log(`\nSprawdzenie ${name}:`);
286 console.log(` Gatunek: ${dino.species}`);
287 console.log(` Sektor: ${dino.sector}`);
288 console.log(` Zdrowie: ${dino.health}%`);
289 } else {
290 console.log(`Nie znaleziono dinozaura: ${name}`);
291 }
292 }
293
294 getDailyReport() {
295 console.log(`\nRAPORT DZIENNY - ${this.parkName}`);
296 console.log("=".repeat(50));
297 console.log(`Status: ${this.status}`);
298 console.log(`Dinozaury: ${this.dinosaurs.length}`);
299 console.log(`Odwiedzający: ${this.visitors}`);
300
301 if (this.dinosaurs.length > 0) {
302 console.log("\nLista dinozaurów:");
303 this.dinosaurs.forEach((dino, i) => {
304 console.log(` ${i + 1}. ${dino.name} (${dino.species}) - Sektor ${dino.sector}`);
305 });
306 }
307 }
308
309 // Metoda używająca setTimeout z arrow function
310 scheduleFeeding(dinoName, delay = 1000) {
311 console.log(`\n⏰ Zaplanowano karmienie ${dinoName} za ${delay}ms`);
312
313 setTimeout(() => {
314 const dino = this.dinosaurs.find(d => d.name === dinoName);
315 if (dino) {
316 console.log(`Karmienie ${dinoName} w ${this.parkName}`);
317 dino.health = Math.min(100, dino.health + 10);
318 }
319 }, delay);
320 }
321}
322
323// Demonstracja systemu
324const management = new JurassicParkManagement("Isla Nublar");
325
326management.openPark();
327management.addDinosaur("Rexy", "Tyrannosaurus Rex", "A");
328management.addDinosaur("Blue", "Velociraptor", "B");
329management.addDinosaur("Trike", "Triceratops", "C");
330
331management.addVisitors(500);
332management.addVisitors(300);
333
334management.checkDinosaur("Blue");
335management.getDailyReport();
336
337management.scheduleFeeding("Rexy", 500);
338management.scheduleFeeding("Blue", 600);
339
340setTimeout(() => {
341 console.log("\n--- Po karmieniu ---");
342 management.checkDinosaur("Rexy");
343 management.checkDinosaur("Blue");
344
345 management.closePark();
346
347 console.log("\nGratulacje! Opanowałeś słowo kluczowe 'this'!");
348}, 800);Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Co oznacza słowo kluczowe 'this' w JavaScript?
2. Na co wskazuje 'this' wewnątrz metody obiektu w JavaScript?
3. Jak zachowuje się 'this' w funkcjach strzałkowych (arrow functions)?
4. W jakim kontekście 'this' wskazuje na nowo tworzony obiekt?