Class

Gdybyśmy popatrzyli na wiele zorientowanych obiektowo języków programowania, zauważylibyśmy, że w wielu z nich obiekty tworzone są na bazie tak zwanych klas, które są swoistą templatką, na bazie której później tworzymy indywidualne instancje/egzemplarze. Każda taka klasa dość często posiada funkcję constructor (przy czym może się ciut różnić jej nazwa) oraz destructor. Ta pierwsza odpalana jest w momencie tworzenia nowej instancji i najczęściej służy do ustawiania podstawowych właściwości, ale i automatycznego odpalania odpowiednich metod. Destructor - przeciwnie - odpalany jest przy usuwaniu danej instancji i służy do czyszczenia wszelkich śmieci, które mogą potencjalnie walać się w pamięci.

Wraz z rozwojem języka Javascript wiele mechanizmów usprawniono, a korzystanie z nich zostało ułatwione. Jednym z takich usprawnień jest wprowadzenie składni class, czyli nowego zapisu na istniejący oparty o prototypy model dziedziczenia. Składnia ta nie zmienia starego mechanizmu, a jest tylko lukrem składniowym (ang. sugar code), czyli przyjemniejszym zapisem tego samego (przy czym nie do końca, bo kilka różnic się tutaj znajdzie 1).

Deklaracja klasy

Ogólna deklaracja klasy ma postać:


class Animal {
    constructor() { }
    method1() { }
    method2() { }
    method3() { }
}

//lub o wiele rzadziej spotykane

const Animal = class {
    constructor() { }
    method1() { }
    method2() { }
    method3() { }
}

//tworzymy instancje tak samo jak poprzednio
const pet1 = new Animal();
const pet2 = new Animal();

Powiedzieliśmy sobie powyżej, że składnia class jest tylko innym zapisem tego co robiliśmy poprzednio za pomocą konstruktor function. Sprawdźmy to za pomocą kodu:


class AnimalA {}
console.log(typeof AnimalA); //function

function AnimalB() {}
console.log(typeof AnimalB); //function

constructor()

Każda klasa posiada specjalną funkcję constructor(), która jest automatycznie odpalana przy tworzeniu nowej instancji za pomocą słowa new.


class Animal {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    method1() { }
    method2() { }
    method3() { }
}

const animal = new Animal("pies", 8);

Funkcja constructor() jest tym samym, co używana w poprzednim zapisie funkcja będąca konstruktorem:


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

//w nowej składni ES6

class PersonCl {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

Żeby stworzyć nową instancję na bazie takiej klasy użyjemy takiego samego zapisu z new co przy starszej składni.


const person1 = new PersonFn("Karol", 15);
const person2 = new PersonCl("Piotr", 18);

Tym razem jednak nie możemy pominąć new:


const person1 = PersonFn("karol", 15); //błędu nie będzie, chociaż kod będzie działał źle
const person2 = PersonCl("Piotr", 18); //błąd - Class constructor Test cannot be invoked without 'new'

Dodawanie metod

Dodając nowe metody i właściwości do prototypu danej klasy w nowej składni nie musimy już pamiętać o składni prototype. Wystarczy, że metody te umieścimy wewnątrz klasy:


class Animal {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    eat() {
        console.log(this.name + " jem");
    }

    sleep() {
        console.log(this.name + " śpię");
    }
}

Tak jak w przypadku starszego zapisu, metody takie automatycznie trafią do prototypu danych obiektów, dzięki czemu wszystkie obiekty budowane na bazie danej klasy będą mogły z nich korzystać.

Class metody

W przeciwieństwie do starszego zapisu metody takie nie będą listowane, jeżeli po danym obiekcie zrobimy pętlę for in, a i w przypadku klas każda metoda ma swoją właściwość name, co czasami może się przydać. Rzeczy te możesz sprawdzić na przykładowej stronie.

Właściwości

Właściwości dla danej instancji dodajemy wewnątrz konstruktora:


class Animal {
    constructor(name) {
        this.legs = 4;
        this.type = "animal";
    }
}

Zapis ten w najnowszej wersji JavaScript został dodatkowo uproszczony, dzięki czemu możemy je definiować także poza konstruktorem:


//równoznaczne z powyższym kodem
class Animal {
    legs = 4;
    type = "animal";
}

class Animal {
    legs = 4;
    type = "animal";

    constructor(name) {
        this.name = name;
    }
}

//jest równoznaczne jest z

class Animal {
    constructor(name) {
        this.name = name;
        this.legs = 4;
        this.type = "animal";
    }
}

Metody statyczne

Jeżeli stworzymy metody danej klasy (tak jak np. poniżej), trafią one do prototypu obiektów tworzonych na bazie tej klasy:


class Human {
    constructor(name) {
        this.name = name;
    }

    say() {
        console.log("Jestem człowiek");
    }
}

Human.say(); //błąd, bo say jest w prototypie
Human.prototype.say(); //"Jestem człowiek"

const ob = new Human("Marcin");
ob.say(); //"Jestem człowiek"

Jak wiesz, dane metody możemy przypisywać nie tylko do prototypu obiektów, ale także bezpośrednio do instancji obiektu:


const ob = new Human("Marcin");
ob.say(); //Jestem człowiek
ob.eat = function() {
    console.log("Jem śniadanie");
}
ob.prototype.eat(); //nie ma, bo tylko powyższa instancja ma tą metodę

W JavaScript możemy też tworzyć metody statyczne, które są dostępne dla danej klasy.

Metody takie nie są dostępne dla nowych instancji, a tylko dla samych klas:


//w ES5
function Human {
    this.name = name;
}

//metoda statyczna
Human.create = function() {
   console.log("Tworzę");
}

Human.prototype.say = function() {
    console.log("Jestem człowiek");
}


const ob = new Human("Marcin");
ob.create(); //błąd
Human.create(); //"Tworzę"

//w ES6
class Human {
    constructor(name) {
        this.name = name;
    }

    say() {
        console.log("Jestem człowiek");
    }

    static create() {
        console.log("Tworzę");
    }
}

const ob = new Human("Marcin");
ob.create(); //błąd
Human.create(); //"Tworzę"

Metody statyczne najczęściej wykorzystywane są do tworzenia użytecznych funkcji dla danej klasy. Można dzięki temu pogrupować funkcjonalności dotyczące danych klas w jednym miejscu:


class User {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    static compareByName(a, b) {
        if (a.name < b.name) return -1;
        if (a.name > b.name) return 1;
        return 0;
    }

    static compareByAge(a, b) {
        return a.age - b.age;
    }
}

const users = [
    new User("Tomek", 10),
    new User("Ania", 35),
    new User("Beata", 20),
    new User("Monika", 20),
    new User("Karol", 22)
];

users.sort( User.compareByName );
console.log( users[0].name ); // Ania

users.sort( User.compareByAge );
console.log( users[0].name ); // Tomek

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę z tego działu, to zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania-obiekty

W repozytorium jest branch "solutions". Tam znajdziesz przykładowe rozwiązania.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.

Menu