OOP w JavaScript:



Podsumowanie podstaw OOP w JavaScript:
    1. Obiekty: Zbiór właściwości i metod, które reprezentują pewne dane i funkcje.
    2. Funkcje konstruktorowe: Używane do tworzenia nowych instancji obiektów.
    3. Prototypy: Mechanizm dziedziczenia metod i właściwości.
    4. Klasy: Wprowadzenie do bardziej strukturalnego podejścia do OOP, bazującego na funkcjach konstruktorach.
    5. Dziedziczenie: Możliwość tworzenia nowych klas na podstawie już istniejących.
    6. Metody statyczne: Metody, które operują na klasie, a nie na instancjach obiektów.
    7. Gettery i Settery: Umożliwiają kontrolowanie odczytu i zapisu wartości właściwości.


Ad1)
    // Tworzenie obiektu
    const person = {
      name: 'Alice',
      age: 25,
      greet() {
        return `Hello, my name is ${this.name}`;
      }
    };

    console.log(person.name);   // 'Alice'
    console.log(person.greet()); // 'Hello, my name is Alice'


Ad2)
    // Funkcja konstruktorowa:
    function Person(name, age) {
      this.name = name;
      this.age = age;
      this.greet = function() {
        return `Hello, my name is ${this.name}`;
      };
    }

    // Tworzenie obiektów (instancji) na podstawie funkcji konstruktora
    const alice = new Person('Alice', 25);
    const bob = new Person('Bob', 30);

    console.log(alice.greet()); // 'Hello, my name is Alice'
    console.log(bob.greet());   // 'Hello, my name is Bob'

Ad3)
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }

    // Dodanie metody do prototypu
    Person.prototype.greet = function() {
      return `Hello, my name is ${this.name}`;
    };

    const alice = new Person('Alice', 25);
    const bob = new Person('Bob', 30);

    console.log(alice.greet()); // 'Hello, my name is Alice'
    console.log(bob.greet());   // 'Hello, my name is Bob'


Ad4)
    // Tworzenie klasy
    class Person {
      // Konstruktor klasy
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }

      // Metoda instancji
      greet() {
        return `Hello, my name is ${this.name}`;
      }
    }

    // Tworzenie obiektów na podstawie klasy
    const alice = new Person('Alice', 25);
    const bob = new Person('Bob', 30);

    console.log(alice.greet()); // 'Hello, my name is Alice'
    console.log(bob.greet());   // 'Hello, my name is Bob'


Ad5)
    // Klasa bazowa
    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }

      greet() {
        return `Hello, my name is ${this.name}`;
      }
    }

    // Klasa dziedzicząca (klasa pochodna)
    class Employee extends Person {
      constructor(name, age, jobTitle) {
        super(name, age); // Wywołanie konstruktora klasy bazowej
        this.jobTitle = jobTitle;
      }

      // Nadpisanie metody greet
      greet() {
        return `Hello, my name is ${this.name} and I am a ${this.jobTitle}`;
      }
    }

    const alice = new Employee('Alice', 25, 'Software Engineer');
    console.log(alice.greet()); // 'Hello, my name is Alice and I am a Software Engineer'


Ad6)
    Metody statyczne (Static Methods)
    class Person {
      constructor(name) {
        this.name = name;
      }

      // Metoda instancji
      greet() {
        return `Hello, my name is ${this.name}`;
      }

      // Metoda statyczna
      static species() {
        return 'Human';
      }
    }

    console.log(Person.species()); // 'Human' - wywołanie metody statycznej na klasie


Ad7)
    # Getter i Setter
    class Person {
      constructor(name) {
        this._name = name;
      }

      // Getter
      get name() {
        return this._name;
      }

      // Setter
      set name(newName) {
        this._name = newName;
      }
    }

    const person = new Person('Alice');
    console.log(person.name); // 'Alice' - użycie getter'a

    person.name = 'Bob';      // użycie setter'a
    console.log(person.name); // 'Bob'