Metody w obiektach

Do tej pory zapoznaliśmy się z obiektami, które przetrzymywały typy prymitywne. Jak już wspomniałem, do pól w obiekcie przypisać możemy każdy typ danych. Przede wszystkim możemy używać funkcje, które przypisane do pola w obiekcie nazywamy metodami.

Metody w obiektach JavaScript

Metody w obiektach JavaScript, mogą być zapisane na różne sposoby. Zobaczmy pierwszy sposób:

const obj1 = {
  print: function(value) {
    console.log(value);
  },
};

obj1.print('Hello'); // 'Hello'
1
2
3
4
5
6
7

W tym przypadku widzimy obiekt z polem print. Jego wartością jest przypisana funkcja. Funkcję przypisujemy tak samo, jakbyśmy przypisali wartość prymitywną. Funkcje, które znajdują się w obiektach, zazwyczaj nazywamy metodami.

Oczywiście nic nie stoi na przeszkodzie, aby do obiektu dodawać kolejne metody, podobnie jak przy dodawaniu zwykłych pól:

obj1.printError = function(err) {
  console.error(err);
};

obj1.printError('Some error'); // 'Some rror'
1
2
3
4
5

Nie ma różnicy między dopisaniem metody do obiektu a dopisaniem zwykłej wartości prymitywnej. Postępujemy dokładnie tak samo.

Jednak metody mogą o wiele więcej niż zwykłe wartości prymitywne. W metodzie możemy na przykład odwołać się do pola w obiekcie i jest to częsta praktyka:

const person = {
  name: 'John',
  printName() {
    console.log(this.name);
  },
};

person.printName();
1
2
3
4
5
6
7
8

Mamy zdefiniowany obiekt, w którym znajduje się pole name oraz metoda printName. Zadaniem metody jest wydrukowanie danych do konsoli. Zauważ, że do pola name odnosimy się przez słowo kluczowe this. Bez tego metoda printName nie będzie w stanie zlokalizować pola name.

Samo this w JavaScript może być na tyle problematyczne, że będzie omówione dokładnie w oddzielnym dziale. Dla uproszczenia powiem, że słowo this wskazuje na ten obiekt i w tym obiekcie będzie poszukiwane pole name.

Getter i setter

W JavaScript istnieją jeszcze specjalne metody jak get i set, które możemy zdefiniować w obiekcie. Nazywane są też akcesorami i często mówi się o nich jako getter i setter.

Getter jest metodą, która zwraca wartość konkretnego pola, a setter metodą, która ustawia wartość konkretnego pola:

const person1 = {
  firstName: 'John',
  get name() {
    return this.firstName;
  },
  set name(name) {
    this.firstName = name;
  },
};
1
2
3
4
5
6
7
8
9

W tym obiekcie mamy zdefiniowane pole firstName oraz akcesory do tego pola. Pierwszym akcesorem jest get. Konstrukcja jest nieco inna niż tworzenie metody. Używamy instrukcji get po czym następuje stworzenie nazwy gettera.

Podobnie jest z akcesorem set. Po instrukcji set tworzymy metodę, która musi przyjąć dokładnie jeden parametr. Nie możemy stworzyć settera bez parametrów, albo z większą ilością niż jeden parametr.

Gdy tworzymy getter i setter dla konkretnego pola w obiekcie, zawsze tworzymy taką samą nazwę. W przypadku gettera i settera nie jest to problem, że oba accessory mają identyczną nazwę.

Getter i setter obsługuje się tak samo, jak pola w obiekcie:

person1.name = 'Rambo';
console.log(person1.name); // 'Rambo'
1
2

Nie wywołujemy settera o nazwie name i nie przekazujemy mu parametru, korzystamy z niego identycznie jak z pola w klasie.

To samo dotyczy wywołania gettera, nie są potrzebna okrągłe nawiasy na końcu jak do wywołania metody. Po prostu odwołujemy się do gettera jak do pola w klasie.

Akcesory mogą być przydatne, gdy bardziej chcemy kontrolować dane zapisywane do określonego pola. Jeśli mamy potrzebę w jakiś sposób kontrolować pole w klasie możemy zrobić to z getterem i setterem:

const person2 = {
  _name: 'Rambo',
  get name() {
    return this.firstName.split('');
  },
  set name(name) {
    this.firstName = name.toUpperCase();
  },
};
1
2
3
4
5
6
7
8
9

W JavaScript jest konwencja, w której podkreślenie przy nazwie pola oznacza, że to pole jest prywatne i nie należy odwoływać się do niego bezpośrednio. W JavaScript do tej pory prywatne pola nie istniały. Jest propozycja ich dodania i być może będziemy mogli ich oficjalnie używać w przyszłości.

Dlatego często używa się zapisu pola z podkreśleniem oraz stworzeniem gettera i settera ale już o normalnej nazwie. Korzystając z tego obiektu będziemy używać accessorów jak normalnego pola obiektu, a pole z podkreśleniem powinno być przez nas omijane.

Dodatkowo getter i setter pozwala nam na dodanie dodatkowej logiki. Możemy w jakiś sposób modyfikować przychodzące i wychodzące wartości.

Pamiętajmy, że akcesory nie zastąpią nam metod w obiektach i nie należy nimi tego robić. Akcesory jak sama nazwa wskazuje, mają dać nam dostęp do danych obiektu. Metody natomiast mogą pełnić różne funkcje w obiekcie.

Metoda jako arrow function

W obiektach możemy także definiować obiekty jako arrow function:

const person3 = {
  firstName: 'John',
  print: () => console.log(this.firstName),
}
person3.print(); // undefined
1
2
3
4
5

Tworzenie metod jako arrow function w obiektach może być atrakcyjne, jednak nie jest to zalecane. Pojawia się tu problem z this, który nie odwołuje się tutaj do obiektu, w którym istnieje metoda, ale w tym przypadku do obiektu globalnego window jeśli mówimy o przeglądarce. O this i problemach tego typu będziemy rozmawiać w oddzielnym dziale.

Dobrą radą natomiast jest nie używanie arrow functions w obiektach JavaScript.

Skrótowy zapis metody

W ES6 pojawił się nowy sposób zapisu metod w obiektach JavaScript:

const person4 = {
  firstName: 'John',
  print() {
    console.log(this.firstName);
  },
};
person4.print(); // 'John'
1
2
3
4
5
6
7

W tym zapisie tworzymy po prostu nazwę funkcji bez słowa kluczowego function. Nie musimy też przypisywać funkcji do pola w obiekcie. Wyglądem przypomina setter i getter. Jest to obecnie dość często stosowany zapis metody w obiektach. Zdecydowanie zmniejsza ilość kodu i poprawia czytelność.

Co warto zapamiętać

  • możemy przypisać funkcję do pola w obiekcie, wtedy staje się ona metodą
  • obiekty mogą posiadać akcesory jak getter i setter
  • nie należy używać arrow function jako metod w obiektach
  • istnieje skrótowy zapis metod w obiektach wprowadzony w ES6