Obiekty w JavaScript - podstawy

Z prostymi obiektami w tym kursie już spotykaliśmy się wielokrotnie. Są to jedne z najpopularniejszych struktur w tym języku podobnie jak tablice. W tym dziale rozłożymy obiekty na czynniki pierwsze i dowiemy się, jak dokładnie działają.

Tworzenie obiektów

Tak samo, jak tablice, obiekty są typami referencyjnymi. Praca z nimi jest nieco inna niż z typem prymitywnym. Tworzymy je jako postać literałowa:

const obj = {};
1

Tworzymy zmienną i przypisujemy do niej dwie klamerki. W ten sposób tworzymy pusty obiekt. Jest to pusty, ale wciąż poprawny obiekt w JavaScript.

Zazwyczaj jednak obiekt trzyma w sobie jakieś properties (właściwości) lub częściej spotykane w polskiej nomenklaturze pola obiektu:

const user = {
  name: 'John',
  surname: 'Rambo',
  'full name': 'John James Rambo',
};
1
2
3
4
5

Każde pole w obiekcie to klucz, czyli key oraz jakaś wartość, czyli value. Dlatego często spotkacie się z nazwą key:value, która po prostu określa pole w obiekcie. Nazwa ta często pojawia się w dokumentacji.

Zwrócę jeszcze uwagę na przypisanie wartości, które następuje po dwukropku, a nie po znaku równości. Kolejne pola obiektu oddzielane są od siebie przecinkiem.

Kluczem może być dowolna nazwa, starajmy się jednak nie używać nazw zastrzeżonych dla JavaScript. Zazwyczaj stosuje się notację camelCase lub jednowyrazowe nazwy. Jest jednak możliwość stworzenia nazwy składającej się z więcej niż dwóch wyrazów. Wtedy zapisujemy ją jako string i ujmujemy w dodatkowe znaki cudzysłowów.

Do pola w obiekcie możemy przypisać każdą wartość. Nie musi być to typ prymitywny, może to być obiekt, funkcja czy tablica.

const obj1 = {
  a: 'text',
  b: [1, 2, 3],
  c: {
    a: 1,
    b: { z: [1, 2, 3] },
  },
  f: function() {
    return 1;
  },
};
1
2
3
4
5
6
7
8
9
10
11

Widzimy tutaj obiekt o wiele bardziej skomplikowany z dodatkowymi zagnieżdżonymi strukturami. Jeżeli mamy wpływ na tworzenie obiektów, dobrze jest zapanować nad ich skomplikowaniem i głębokością. Z takimi obiektami pracuje się gorzej i zawsze jest większa możliwość popełnienia błędu.

Ciekawostką jest także to, że możemy tworzyć pola, których nazwy są bardziej dynamiczne:

const obj2 = {
  [Math.random()]: 'random',
};
console.log(obj2); // { '0.9301655569559864': 'random' }
1
2
3
4

Jest to już skrajny przypadek, ale ten obiekt jako klucz posiada randomową liczbę. Tym razem klucz jest definiowany w nawiasach kwadratowych. Między nimi może znaleźć się dowolne wyrażenie JavaScript. Ja akurat wywołuję konkretną metodę Math.random().

Wbrew pozorom taka możliwość przydaje się, gdy chcemy tworzyć obiekty w dynamiczny sposób na przykład jakimś generatorem, a kluczem mają być kolejne liczby lub na przykład aktualna data. Z takimi obiektami pracuje się nieco inaczej i później poznamy różne metody, które w tym pomagają.

Odczyt danych

Dane z obiektów możemy odczytać przynajmniej na dwa sposoby.

Pierwszy z nich to notacja z kropką:

console.log(user.name); // 'John'
1

Odnosimy się do obiektu i po kropce do odpowiedniego pola w obiekcie. W ten sposób pobieramy wartość.

Drugi zapis to notacja w nawiasach kwadratowych nazywana bracket notation:

console.log(user['name']); // 'John'
console.log(user['full name']); // 'John'
1
2

W tym przypadku odwołujemy się do obiektu, ale nie odwołujemy się już przez kropkę. Podobnie jak przy tablicy używamy nawiasy kwadratowe i podajemy w nich nazwę pola. Nazwa musi być podana jako wartość string.

Zauważ, że jest to też jedyna możliwość, aby dostać się do pól zadeklarowanych jako string. Nie było by to możliwe z użyciem kropki.

Zapis bracket notation przydaje się także, gdy nazwa pola przetrzymywana jest w zmiennej, a zdarzy się tak niejednokrotnie w naszym kodzie JavaScript:

const fullName = 'full name';
console.log(user[fullName]);
1
2

Zazwyczaj pisząc aplikację, używamy notacji z kropką. Jednak w niektórych sytuacjach brakcet notation jest bardzo pomocne.

Jeśli chcemy odwoływać się do pół zagnieżdżonych, wstawiamy kolejne kropki:

console.log(obj1.c.b.z) // [ 1, 2, 3 ]
console.log(obj1['c']['b']['z']) // [ 1, 2, 3 ]
1
2

lub kolejne nawiasy kwadratowe. Widzimy, że notacja z kropką ma tutaj przewagę w czytelności i zwięzłości kodu.

Może się przydarzyć sytuacja w której, nie uda nam się odczytać pola, bo takie nie istnieje:

console.log(user.enemy) // undefine
console.log(user.enemy.name) // error
1
2

Jeżeli odwołamy się do pola, które nie istnieje w obiekcie, otrzymamy wartość undefined. JavaScript nie zgłasza błędu. Czasem jednak próbujemy dalej grzebać w obiekcie i wywołanie kolejnego pola na undefined wywoła błąd.

Takie obiekty z niepewną strukturą i brakującymi polami mogą się zdarzyć. Dlatego stosujemy wtedy serię instrukcji if lub optional chaining, które poznaliśmy w poprzednich częściach kursu.

Zapis danych

Przypisanie nowej wartości do obiektu jest bardzo proste:

user.surname = 'Rambo 3';
1

Odwołujemy się przez znak przypisania, a nie przez dwukropek jak przy tworzeniu obiektu. W ten sposób przypisujemy nową wartość do pola obiektu.

Możemy oczywiście przypisać każdą wartość na przykład undefined:

user.surname = undefined;

console.log(user.surname); // undefined
console.log(user.xyz); // undefined
1
2
3
4

Gdy przypiszemy wartość undefined i odwołamy się do tego pola, otrzymamy oczywiście wartość undefined. Również taką wartość otrzymamy, gdy odwołamy się do pola, które nie istnieje.

Dlatego należy być ostrożnym, gdy operujemy na polach obiektu i otrzymujemy undefined. Może być to albo wartość tego pola, albo takie pole po prostu nie istnieje. Potem poznamy dodatkowe metody, które pozwalają stwierdzić czy konkretne pola istnieją w obiekcie.

Modyfikacja obiektu

W JavaScript możemy nie tylko modyfikować pola obiektu, ale także sam obiekt, dodając do niego nowe właściwości:

user.address = 'Jungle';
console.log(user);
1
2

Jest to bardzo proste. Odwołujemy się do obiektu i po kropce wstawiamy nazwę, którą chcemy dodać do obiektu.

  {
    name: 'John',
    surname: undefined,
    'full name': 'John James Rambo',
    address: 'Jungle'
  }
1
2
3
4
5
6

Od tego momentu obiekt będzie posiadał nowe pole. W JavaScript często możecie się spotkać z takimi modyfikacjami w locie. W jednym miejscu obiekt nie będzie miał jakiegoś pola, a w drugim miejscu już będzie miał dodane nowe właściwości. Możemy w ten sposób dodawać nie tylko wartości prymitywne, ale funkcje, tablice i inne obiekty.

Praca z takimi obiektami może być kłopotliwa. Dlatego starajmy się przewidzieć, jakie pola będzie posiadał obiekt i zadeklarować obiekt ze wszystkimi niezbędnymi polami. Do aktualnie nieużywanych pól można przypisać wartość null.

Nie tylko możemy modyfikować obiekty, dodając do nich nowe pola. Możemy też całkowicie usuwać pola z obiektów:

delete user.address;
1

Używamy instrukcji delete, która całkowicie usuwa pole z obiektu. Nie jest ono ustawione na null czy undefined tylko całkowicie usuwane.

  {
  name: 'John',
    surname
:
  undefined,
    'full name'
:
  'John James Rambo',
}
1
2
3
4
5
6
7
8
9

Operator ten jest jednak bardzo wolny. Jeżeli chcemy zmienić obiekt, być może warto przepisać obiekt tylko z tymi polami, które chcemy w dalszej części aplikacji obsługiwać, są na to różne sposoby, które potem poznamy.

W mojej opinii czasami lepiej stworzyć nowy obiekt na bazie istniejącego niż manipulować nim tak mocno przez dodawanie nowych pól czy usuwanie ich. Kod wtedy może być bardziej zrozumiały, a także nasz edytor kodu analizujący kod łatwiej wychwyci możliwość popełnienia błędu.

Skrót do tworzenia obiektów

W specyfikacji ES6 pojawiła się jeszcze ciekawa opcja tworzenie obiektu w nieco krótszy sposób. Dotyczy to głównie obiektów zwracanych z jakichś funkcji lub obiektów tworzonych z danych:

const name = 'John';
const surname = 'Rambo';

const person = {
  name,
  surname,
};
1
2
3
4
5
6
7

W tym przykładzie mamy dwie zmienne, z których chcemy stworzyć obiekt. Możemy skorzystać ze skrótu i stworzyć obiekt wstawiając zmienne między dwie klamry. Nazwy tych zmiennych będą nazwami kluczy, a wartości zmiennych staną się wartościami obiektu.

Jest to oczywiście odpowiednik tego zapisu:

const person1 = {
  name: name,
  surname: surname,
};
1
2
3
4

W taki sposób tworzyło się obiekty z gotowych danych, zanim pojawiła się nowa opcja w ES6.

Ten krótki zapis często wykorzystywany jest w funkcjach JavaScript:

function create(name, surname) {
  return { name, surname };
}

console.log(create('John', 'Rambo')); // { name: 'John', surname: 'Rambo' }
1
2
3
4
5

Tutaj mamy funkcję, która z otrzymanych parametrów tworzy obiekt i zwraca go. Oczywiści jest to uproszczony przykład. Często jednak będziecie się spotykać w JavaScript właśnie z taką formą tworzenia obiektów.

Co warto zapamiętać

  • obiekty tworzymy jako postać literałowa
  • pole obiektu reprezentowane jest przez klucz - wartość
  • kluczem może być niemalże dowolną wartość, przy wartości string używamy cudzysłowów, przy wartościach dynamicznych nawiasów kwadratowych
  • wartością może być wartość prymitywna, funkcja, obiekt czy tablica, dosłownie każda struktura
  • do pól odwołujemy się przez kropkę lub nawiasy kwadratowe
  • możemy w czasie działania aplikacji dodawać nowe pola lub je usuwać za pomocą delete
  • z gotowych danych można tworzyć obiekty w krótszej formie