Obiekty w JavaScript

Cześć! To jest fragment książki JavaScript od podstaw, która ma pomóc w nauce programowania od zera.

Definicja obiektu

W poprzednich rozdziałach nauczyliśmy się jak przechowywać wartości w zmiennych. Oto kilka przykładów:

let userName = "Triss";
let userSurname = "Merigold";
let userUseMagic = true;

Z czasem jednak liczba takich wartości w programie rośnie i pojawia się potrzeba zorganizowania ich w grupy. Dla przykładu: wszystkie powyższe wartości moglibyśmy trzymać razem, jako wartości dotyczące użytkownika. Do tego wykorzystujemy obiekty, o których można myśleć jak o zbiorach zmiennych.

Obiekty tworzymy przy użyciu nawiasu klamrowego {}. Wewnątrz definiujemy zmienne, nazywane właściwościami obiektu (od angielskiego property)1. Definicję każdej właściwości zaczynamy od jej nazwy, a następnie po dwukropku podajemy wartość początkową. Właściwości oddzielamy przecinkami.

const user = {
   name: "Triss",
   surname: "Merigold",
   useMagic: true,
};

Wartości z obiektów odczytujemy poprzez postawienie kropki między zmienną wskazującą na obiekt, a zmienną zdefiniowaną w tym obiekcie. Przykładowo, aby odczytać imię, powinniśmy zapytać o user.name.

console.log(user.name); // Triss
console.log(user.useMagic); // true

Takie wywołanie obiektu nie jest nam obce. console również jest obiektem, a log to jego właściwość (konkretnie metoda, czyli właściwość o typie funkcyjnym). Podobnie Math i jego właściwości takie jak PI czy min.

Przy użyciu console.log jesteśmy w stanie wyświetlić reprezentację całego obiektu.

console.log(user);
// {name: "Triss", surname: "Merigold", useMagic: true}

Właściwości są zmienne, a więc tak jakby były zdefiniowane przy użyciu słówka let. Ustawiamy ich wartości podobnie jak je odczytujemy. Odnosimy się do właściwości, po czym po znaku równości określamy nową wartość. Przykładowo user.name = "Nieulękła" aby zmienić wartość właściwości name w obiekcie user na "Nieulękła".

console.log(user.name); // Triss
user.name = "Nieulękła";
console.log(user.name); // Nieulękła

console.log(user);
// {name:"Nieulękła",surname:"Merigold",useMagic:true}

Obiekty są jednym z kluczowych konceptów w programowaniu i mają znacznie więcej funkcji niż tylko grupowanie podobnych wartości. Będzie to lepiej widoczne przy iterowaniu po tablicach, które najczęściej przechowują zbiór obiektów. Na przykład zbiór produktów w sklepie internetowym czy zbiór wpisów na blogu. Wspomnimy też o popularnej koncepcji programowania obiektowego, która nomen omen opiera się na obiektach.

Może Cię zastanawiać dlaczego użyłem słówka const zamiast let przed definicją user. W praktyce, gdybym użył let, to nic by się nie zmieniło ani w powyższych, ani poniższych przykładach. To słówko określa czy zmienna może się zmienić, czy nie, nie wpływa na obiekt, na który wskazuje. Dzięki const zmienna ta zawsze będzie wskazywała na ten sam obiekt, choć jego właściwości nadal mogą się zmieniać.

Obiekt jako właściwość

Nic nie stoi na przeszkodzie, aby obiekt służył jako właściwość w innym obiekcie.

const user = {
   name: "Aaron",
   address: {
       street: "Demegara 13",
       city: "Agan"
   }
};

Aby odnieść się do obiektu reprezentującego adres, musimy odnieść się do właściwości address w obiekcie user, a więc user.address. Jeśli jednak potrzebowalibyśmy się odnieść do właściwości street w tym obiekcie, to należy dodatkowo zapytać o tę właściwość poprzez user.address.street. Analogicznie, gdybyśmy chcieli modyfikować tę wartość.

console.log(user.address.city); // Agan
user.address.city = "Cerekau";
console.log(user.address.city); // Cerekau

console.log(user.address.street); // Demegara 13

Ćwiczenie: Obiekty

W poniższym kodzie wydziel obiekty ze zmiennych. Dla przykładu userNameuserSurname może być jednym obiektem user z właściwościami namesurname. Zmienna userAddresPostal może być właściwością postal o obiekcie address, znajdującym się w obiekcie user.

let userName = "Kuba";
let userSurname = "Wędrowycz";
let userAddressCountry = "Polska";
let userAddressCity = "Stary Majdan";
let userAddressPostal = "22-120";
let userAddressStreet = null;

let bookTitle = "Karpie bijem";
let bookReleaseYear = 2019;

// Użycie
console.log(userSurname); // Wędrowycz
userName = "Jakub";
console.log(userAddressCity); // Stary Majdan
userAddressStreet = "Bagnowska";

console.log(bookReleaseYear); // 2019

Odpowiedzi na końcu książki.

Modyfikowanie obiektów

Każdy obiekt jest osobnym bytem. To, że wyglądają podobnie, nie znaczy, że mają na siebie wpływ. Dlatego też w poniższym przykładzie zmiana name w obiekcie user1 nie będzie miała żadnego wpływu na user2.

const user1 = { name: "Rafał" };
const user2 = { name: "Rafał" };
 
console.log(user1.name); // Rafał
console.log(user2.name); // Rafał

user1.name = "Bartek";
 
console.log(user1.name); // Bartek
console.log(user2.name); // Rafał

Z drugiej strony, jeśli mamy dwie zmienne wskazujące na jeden obiekt, to możemy go zmienić przy użyciu dowolnej z nich. Po takim zabiegu, wartości dla obu zmiennych ulegną zmianie, bo przecież przekształcone zostało coś, na co obydwie wskazują.

const user1 = { name: "Rafał" };
const user2 = user1;

console.log(user1.name); // Rafał
console.log(user2.name); // Rafał

user1.name = "Bartek";

console.log(user1.name); // Bartek
console.log(user2.name); // Bartek

Dwie zmienne wskazują na ten sam obiekt i właściwość tego obiektu zmienia wartość.

Warto porównać to z przykładem, gdy dwa obiekty pokazywały na tą samą wartość, a potem zmieniło się to, na co jedna z tych zmiennych wskazuje. Wynik będzie inny.

let user1 = { name: "Rafał" };
const user2 = user1;

console.log(user1.name); // Rafał
console.log(user2.name); // Rafał

user1 = { name: "Bartek" };

console.log(user1.name); // Bartek
console.log(user2.name); // Rafał

Najpierw dwie zmienne wskazują na ten sam obiekt, następnie zmienna user1 zaczyna wskazywać na inny.

Dodawanie właściwości

Jeśli w obiekcie brakuje nam właściwości, zawsze można ją dodać. Wygląda to tak samo jak ustawianie jej wartości.

const user = { name: "Mateusz" };
user.surname = "Bogolubow";

console.log(user.surname); // Bogolubow

Osobiście operacja ta kojarzy mi się z doklejaniem wartości przy użyciu karteczki samoprzylepnej.

Brakujące właściwości

Może Cię zastanawiać co w sytuacji, gdy brakuje jakiejś wartości w obiekcie. Podobnie jak w przypadku zwykłych zmiennych zwracana jest wtedy wartość undefined.

const user = { name: "Samuel" };
console.log(user.surname); // undefined

Właściwości, które mogą się znajdować albo nie nazywamy opcjonalnymi. Używamy ich dopiero po sprawdzeniu, czy posiadają one wartość, a robimy to przez przyrównanie do undefined.

function printUserSurname(user) {
   if(user.surname !== undefined) {
      console.log(user.surname);
   } else {
      console.log("nieznane");
   }
}

printUserSurname({ name: "Samuel" }); // nieznane
printUserSurname({ name: "Samuel", surname: "Vimes" });
// Vimes

Czasem chcemy sprawdzić, czy właściwość nie jest ani undefined, ani null. W takim przypadku programiści JavaScript często po prostu umieszczają wartość tej właściwości w warunku (warto jednak pamiętać, że takie sprawdzenie nie przejdzie także dla innych wartości będących falsy).

function printUserSurname(user) {
   if(user.surname) {
      console.log(user.surname);
   } else {
      console.log("nieznane");
   }
}

printUserSurname({ name: "Samuel" }); // nieznane
printUserSurname({ name: "Samuel", surname: null });
// nieznane
printUserSurname({ name: "Samuel", surname: "Vimes" });
// Vimes

Ćwiczenie: Zabawa z obiektami

Uzupełnij luki w poniższym kodzie.

let obj = {};
const obj2 = obj;

obj.name = ______;
console.log(obj.name); // Alek
console.log(obj2.name); // ______

const obj3 = { name: obj.____ };
console.log(obj3); // {name: "Alek"}

obj = { size: "S" };
console.log(obj.name); // ________
console.log(obj.size); // ________
console.log(obj2.name); // ________

Powyższe użycie jest nadmiernie zagmatwane na potrzebę przećwiczenia tego, czego się nauczyliśmy. Zwykle piszemy kod tak, żebyśmy nie musieli się zastanawiać co jest pod jaką właściwością i w jakim obiekcie. Warto jednak dobrze to wyćwiczyć, bo jeśli pomylimy się w projekcie, to odnalezienie błędu może być czasochłonne.

Odpowiedzi na końcu książki.

Format zapisu danych JSON

To, jak zapisujemy obiekty w JavaScript, wyszło daleko poza sam język. Załóżmy, że użytkownik Twojej strony podał Ci swoje dane — adres, opis profilu itp. Aby informacje nie przepadły, musisz coś z nimi zrobić. Przechowywanie w zmiennych nie wystarcza, gdyż stracimy je przy pierwszym odświeżaniu strony. Musimy je gdzieś zapisać, albo wysłać tam, gdzie będą bezpieczne. Zazwyczaj wysyłamy je na serwer, czyli komputer, który obsługuje wszystkich użytkowników i zbiera tego typu dane w jednym miejscu. Pytanie tylko, jak je prześlemy. Załóżmy, że mamy tych danych bardzo dużo, więc wysyłanie ich pojedynczo wymagałoby niepotrzebnej pracy. Powinniśmy je w jakiś sposób zamienić w jeden tekst, który będzie zawierał wszystkie wartości oraz informacje, co która z nich oznacza. I tu właśnie przychodzi nam z pomocą JSON, czyli JavaScript Object Notation. Jest on formatem zapisu danych wzorowanym na tym, jak są one zapisywane w JavaScript. Spodziewa się on, że dane będą przekazane jako jeden wielki obiekt, który zostanie zapisany jako tekst. Nazwy właściwości w JSON muszą być w cudzysłowie (w JavaScript jest to opcjonalne). Poza tym nie pozwala na żadne zmienne, funkcje czy inne elementy języka JavaScript. Oto przykładowy JSON:

{
  "name": "Roger",
  "surname": "Stackdon",
  "age": 63,
  "role": {
    "status": "MEMBER",
    "position": "Kadet"
  }
}

Uwaga: Przy tworzeniu obiektu w formacie JSON zwróć szczególną uwagę na to, gdzie stawiasz przecinki. W każdym obiekcie muszą się one znaleźć po każdej właściwości poza ostatnią.

Gdybyśmy podejrzeli w jaki sposób komunikują się ze sobą dwa serwisy w internecie, na przykład Facebook i Google, to niemalże na pewno zobaczylibyśmy odpowiedź w formacie JSON.

Dla przykładu, gdyby jakiś serwis zapytał Google Pay o dane płatności dla określonego użytkownika, odpowiedź byłaby podobna do poniższej.

{
  "apiVersion": 2,
  "apiVersionMinor": 0,
  "paymentMethodData": {
    "type": "CARD",
    "description": "Visa••••1234",
    "info": {
      "cardNetwork": "VISA",
      "cardDetails": "1234"
    },
    "tokenizationData": {
      "type": "PAYMENT_GATEWAY",
      "token": "examplePaymentMethodToken"
    }
  }
}

W formacie JSON możemy umieścić także tablice, które poznamy dopiero później.

Obiekt w JavaScript możemy zamienić na stringa w formacie JSON przy użyciu metody JSON.stringify, a stringa na obiekt przy użyciu JSON.parse.

const user = {
  name: "Jean",
  surname: "Tarrou"
}
const userInJson = JSON.stringify(user);
console.log(userInJson) 
// {"name":"Jean","surname":"Tarrou"}
console.log(typeof userInJson) // string

const user2 = JSON.parse(userInJson);
console.log(user2) // {name: "Jean", surname: "Tarrou"}
console.log(typeof user2) // object

Ćwiczenie: JSON

Tworzysz stronę produktu w obuwniczym sklepie internetowym. Dla każdego produktu potrzebujesz wyświetlić:

  • nazwę, np "Air Max 720",
  • markę, np. "Nike",
  • opis, np. "Mają największą, jak dotąd, poduszkę gazową Air...",
  • rozmiar, np. 45,
  • adres URL obrazka, np. "https://example.pl/path/to/airmax.png",
  • cenę, zawierającą kwotę, np. 649.99, oraz walutę, np. "PLN" (czyli nasz polski złoty).

Zaprojektuj, jak mogłaby wyglądać odpowiedź zawierająca te dane w formacie JSON. Przykładowa odpowiedź na końcu rozdziału.

Odpowiedzi na końcu książki.

Uwaga: Kwoty z częścią dziesiętną, które muszą być precyzyjne, takie jak ceny, zapisuje się w formacie JSON jako stringi. Robi się tak, aby uniknąć problemu z zaokrągleniami w niektórych językach, przez który mogłyby stracić grosz.

1:

W niektórych źródłach używa się pojęcia pola (of ang. field).