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

Słowo kluczowe this w JavaScript

13 min czytania
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:

  1. W kontekście globalnym
  2. Jako metoda obiektu
  3. Jako konstruktor
  4. Za pomocą metod call(), apply() lub bind()
  5. 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:

  1. 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};
  1. Zachowaj referencję do this w 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};
  1. 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 blueTracker

Rozwiązania:

  1. Użyj funkcji strzałkowej:
1setTimeout(() => blueTracker.startTracking(), 1000);
  1. 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:

  1. W globalnym kontekście: this to window w przeglądarce i module.exports ({}) w pliku Node.js; w zwykłej funkcji poza trybem ścisłym - obiekt globalny (globalThis).

  2. W metodzie obiektu: this odnosi się do obiektu, który wywołał metodę.

  3. W konstruktorze: this odnosi się do nowo tworzonego obiektu.

  4. W funkcjach strzałkowych: this jest dziedziczone z otaczającego zakresu leksykalnego.

  5. Przy użyciu call/apply/bind: this jest jawnie określone przez programistę.

Najlepsze praktyki:

  1. Używaj funkcji strzałkowych dla funkcji zwrotnych (callbacks) i funkcji zagnieżdżonych, aby zachować kontekst this.

  2. Używaj metod call(), apply() lub bind(), gdy potrzebujesz jawnie kontrolować wartość this.

  3. Unikaj tworzenia metod w konstruktorach, zamiast tego dodawaj je do prototypu, aby uniknąć duplikacji kodu.

  4. Pamiętaj, że this jest ustalane podczas wywoływania funkcji, a nie podczas jej definiowania.

  5. 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. 1. Co oznacza słowo kluczowe 'this' w JavaScript?

  2. 2. Na co wskazuje 'this' wewnątrz metody obiektu w JavaScript?

  3. 3. Jak zachowuje się 'this' w funkcjach strzałkowych (arrow functions)?

  4. 4. W jakim kontekście 'this' wskazuje na nowo tworzony obiekt?

Przydatne artykuły