Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!

🔥 Webinar Q&A na temat IT – pytaj, o co chcesz! już 24.11.2022 (czwartek) o 20:30

Obiekty w JavaScript – czym są i jak z nich korzystać

Zacznij tworzyć użyteczne struktury danych!

W tym artykule znajdziesz dawkę podstaw na temat obiektów w JavaScripcie i kilka odsyłaczy do rozszerzenia tematu – poznasz więc lub utrwalisz sposób pracy z obiektami i będziesz mógł też uzupełnić wiedzę, korzystając z dodatkowych źródeł. Jeśli na razie rzadko korzystasz z obiektów, to mam nadzieję, że ten artykuł Cię do nich zachęci!

Spis treści

 Czym jest obiekt JavaScript

Można powiedzieć, że obiekt w JS to odzwierciedlenie otaczających nas rzeczy. Gdy rozejrzymy się wokół, zobaczymy np. klawiaturę, telefon, kota, autobus itd. Każdy z tych elementów ma pewne cechy (właściwości) i funkcje (metody), czyli jakoś wygląda i coś robi / czemuś służy.

Takie właściwości i metody mógłby mieć obiekt tortoise.

obiekt-tortoise

Jeśli spojrzymy na to bardziej z poziomu programowania, obiekt to pojemnik na wartości. Możemy w nim przechowywać właściwie wszystko: typy proste (ciągi znaków, liczby itd.), funkcje, tablice i inne obiekty i wygodnie się do nich odwoływać.

 Budowa obiektu w JavaScript

Obiekt składa się z nawiasów klamrowych, które otaczają wymienione po przecinku tzw. pary klucz: wartość. Kluczem jest nazwa właściwości lub metody, a wartością to, co jest do niej przypisane po dwukropku.

Długopis, który jest produktem w sklepie internetowym, moglibyśmy zaprezentować w poniższy sposób:

Właściwości i metody w obiekcie JavaScript

Na grafice mamy obiekt: pen. Długopis ma cztery właściwości: kolor tuszu, cenę, informację o tym, czy jest na stanie, oraz wysokość obniżki. Ma także jedną metodę: możliwość obliczenia ceny po uwzględnieniu obniżki.

Widzisz tam również słowo kluczowe this. Wskazuje ono na obiekt, w kontekście którego zostaje użyte. Dzięki niemu w metodzie możemy odwołać się do ceny długopisu i wysokości obniżki. O tym przeczytasz jeszcze na końcu artykułu.

 Odwoływanie się do właściwości i metod obiektu

 Odwoływanie się po kropce

Aby dotrzeć do wartości, np. ceny długopisu, możemy odwołać się do odpowiedniej właściwości za pomocą zapisu pen.price.

const pen = {
    inkColor: 'blue',
    price: 12,
    isInStock: true,
    discount: 0.2,
    countCurrentPrice: function () {
        const newPrice = this.price * (1 - this.discount);
        const priceRounded = (Math.round(newPrice * 100) / 100).toFixed(2);
        return priceRounded;
    },
};

console.log(pen.price); // 12
console.log(pen.countCurrentPrice()) // "9.60"

A jeśli chcielibyśmy uruchomić metodę countCurrentPrice, to zrobimy to tak: pen.countCurrentPrice(). Dochodzą nam więc tylko nawiasy okrągłe, tak samo jak przy zwykłym wywoływaniu funkcji.

 Odwoływanie się po nawiasach kwadratowych

Nazwa właściwości z dodatkowymi znakami

Odwołanie się po kropce nie sprawdzi się tam, gdzie nazwa klucza zawiera dodatkowe znaki, np. zamiast inkColor mamy "ink-color". Wówczas zapis pen.ink-color spowoduje błąd. Pozostaje
nam więc opcja zapisu: pen["ink-color"].

const pen = {
    'ink-color': 'blue',
};

console.log(pen['ink-color']); // "blue"

Prawdziwa siła nawiasów kwadratowych

Nawiasy kwadratowe doskonale sprawdzają się wówczas, gdy nazwę właściwości (klucza) przechowujemy w zmiennej. Załóżmy, że chcę uzyskać konkretne informacje o danym długopisie. Nie muszę za każdym razem odwoływać się do kolejnych właściwości jak poniżej:

const penLight = {
    inkColor: 'blue',
    price: 12,
    isInStock: true,
    discount: 0.2,
    countCurrentPrice: function () {
        const newPrice = this.price * (1 - this.discount);
        const priceRounded = (Math.round(newPrice * 100) / 100).toFixed(2);
        return priceRounded;
    },
};

const penDark = {
    inkColor: 'black',
    price: 9,
    isInStock: true,
    discount: 0,
    countCurrentPrice: function () {
        const newPrice = this.price * (1 - this.discount);
        const priceRounded = (Math.round(newPrice * 100) / 100).toFixed(2);
        return priceRounded;
    },
};

console.log(penLight.inkColor); // "blue"
console.log(penDark.inkColor); // "black"
console.log(penLight.countCurrentPrice()); // "9.60"
console.log(penDark.countCurrentPrice()); // "9.00"

console.log(penLight['inkColor']); // "blue"
console.log(penDark['inkColor']); // "black"
console.log(penLight['countCurrentPrice']()); // "9.60"
console.log(penDark['countCurrentPrice']()); // "9.00"

Wystarczy, że w jednym miejscu zgromadzę nazwy kluczy i będę po nich iterował.

const infos = ['inkColor', 'price', 'isInStock'];
infos.forEach(function (info) {
    console.log(penLight[info]);
});
// "blue"
// 12
// true

infos.forEach(function (info) {
    console.log(penDark[info]);
});
// "black"
// 9
// true

A jeśli będę chciał wywołać kilka metod obiektu i ich nazwy zgromadzę sobie w tablicy, to wystarczy, że za nawiasami kwadratowymi dodam okrągłe – jak przy wywołaniu funkcji (bo właśnie to robię, wywołuję funkcję):

const methods = ['countCurrentPrice']; // tu akurat mam tylko jedną metodę

methods.forEach(function (method) {
    console.log(penLight[method]());
});
// "9.60"

Zobacz, jak bardzo skraca i ułatwia nam to zapis!

Rozwiązanie takie – gdzie nazwy kluczy przechowujemy w zmiennych – nazywane jest często dynamicznym pobieraniem danych. Jest to wykorzystywane np. w Reakcie, gdy przekazujemy informacje między różnymi komponentami. Warto więc znać ten mechanizm.

Jeżeli zainteresował Cię ten temat i umiesz już obsługiwać formularze, zapraszam do lektury artykułu „Walidacja formularza w JavaScript” – tam właśnie wykorzystanie powyższych możliwości obiektów i JS pozwoliło nam znacznie uprościć kod.

 Obiekt jako wartość właściwości, czyli obiekt zagnieżdżony

Jak już wspomniałem, w obiekcie możemy przechowywać właściwie wszystko – także inne obiekty. Spójrzmy na poniższy przykład biurka, które ma w zestawie półki. Jeżeli chcielibyśmy sprawdzić, jakie kolory półek są dostępne, wystarczy zapis: desk.shelves.material (lub z nawiasami kwadratowymi). W ten sposób otrzymamy dostęp do tablicy z materiałami.

const desk = {
    topMaterial: ['black', 'white', 'oak'],
    shelves: {
        material: ['black', 'white'],
    },
};
console.log(desk.shelves.material); // ["black", "white"]
console.log(desk['shelves']['material']); // ["black", "white"]

Podobnie będzie z metodą:

const mobile = {
    screen: {
        turnOn: function () {
            return 'screen is active';
        },
    },
};
console.log(mobile.screen.turnOn()); // "screen is active"
console.log(mobile['screen']['turnOn']()); // "screen is active"

Teoretycznie obiekty możemy zagnieżdżać w nieskończoność, lecz głębokie zagnieżdżenia rodzą problemy z dostępem do wartości. Więcej o tym przeczytasz we fragmencie artykułu o rekurencji: „Struktura danych na podstawie magazynu”.

 Dodawanie i usuwanie właściwości oraz metod

 Tworzenie nowych par klucz: wartość i nadpisywanie istniejących

To, co chcemy dodać, wystarczy zapisać podobnie jak przy odwoływaniu się do istniejących kluczy. Po kropce lub w nawiasie kwadratowym wpisujemy nowy klucz, a za pomocą znaku równości przypisujemy do niego wartość.

const desk = {
    topMaterial: ['black', 'white', 'oak'],
    shelves: {
        material: ['black', 'white'],
    },
};

desk.price = 120;
desk['discount'] = 0.1;
desk.legs = {
    material: ['satin', 'black'],
};
desk.countCurrentPrice = function () {
    const newPrice = this.price * (1 - this.discount);
    const priceRounded = (Math.round(newPrice * 100) / 100).toFixed(2);
    return priceRounded;
};

Jeśli teraz ponownie wyświetlisz w konsoli obiekt desk, to zobaczysz, że posiada on nowe metody i właściwości.

Nadpisywanie istniejących właściwości i metod odbywa się w identyczny sposób.

 Usuwanie właściwości i metod z obiektu

Wystarczy wpisać słowo delete i zaraz za nim odwołać się do właściwości lub metody, którą chcemy usunąć (znów po kropce lub w nawiasie kwadratowym): delete desk.legs.

Jeśli chcesz wiedzieć więcej o operatorze delete, to informacje znajdziesz w dokumentacji MDN.

 Iteracja – pętla po właściwościach obiektu

 Pętla for…in

Dzięki pętli for…in możemy iterować po wszystkich kluczach obiektu. Pamiętaj jednak, że iteracja nie musi odbywać się w kolejności, w której w swoim kodzie wypisałeś klucze, dlatego traktuj obiekty jako zbiór nieuporządkowanych właściwości i metod.

const piano = {
    width: 140,
    price: 3200,
    brand: 'Yamaha',
    id: '1Dh34B',
};

for (const key in piano) {
    //jeśli typ wartości to number
    if (typeof piano[key] === 'number') {
        // wyświetl w konsoli nazwę klucza i przechowywaną wartość
        console.log(key + ': ' + piano[key]);
    }
}

W pętli for…in pod key podstawione zostaną kolejne klucze obiektu (możesz tę zmienną nazwać, jak chcesz). Jeśli więc do ceny odwołalibyśmy się przez zapis piano["price"] – czyli obiekt["klucz"] – to to samo stanie się w pętli dzięki zapisowi piano[key].

 Pętla for…in a dziedziczenie

Gdy w nauce JavaScriptu dotrzesz do dziedziczenia prototypowego, poświęć ponownie chwilę na pętlę for…in. Nie bierze ona bowiem pod uwagę jedynie własnych kluczy obiektu, lecz także te dziedziczone. Tak samo działa operator in.

Żeby krótko naświetlić Ci sytuację, przeanalizujmy prosty przykład. Załóżmy, że masz obiekt present. Chcesz stworzyć podobne obiekty o kilku dodatkowych cechach, np. różowy prezent dla matki, duży dla babci i grający dla dziecka. Ponieważ obiekt prezent posiada już potrzebne Ci cechy (pudełko, wstążkę), „pożyczysz” jego właściwości i metody dla pozostałych obiektów: presentForMum, presentForGrandmum, presentForChild. Wówczas powiemy, że obiekty te dziedziczą po obiekcie present. Tym samym pętla for…in będzie iterować nie tylko po nowych kluczach w tych nowych obiektach, lecz także po tym, co odziedziczyły one z obiektu present.

Jak sobie z tym radzić, przeczytasz w punkcie „Iterowanie jedynie po własnych właściwościach” w dokumentacji MDN.

 Kopiowanie obiektu w JavaScript

To, o czym przede wszystkim musisz pamiętać, to że obiekt, podobnie jak tablica i funkcja, jest typem złożonym. Nie skopiujemy go więc przez przypisanie do nowej zmiennej. Taka operacja spowoduje jedynie to, że nasza nowa zmienna będzie wskazywać cały czas na ten sam obiekt.

Jeśli więc za jej pomocą zmienimy coś w obiekcie, zmienimy to także „u źródła”. W przypadku typów złożonych zmienne nie przechowują bowiem wartości takiej, jaką widzimy (tablicy, obiektu, funkcji), lecz jedynie referencję do miejsca w pamięci. Dopiero w tamtym miejscu przechowywany jest obiekt/tablica/funkcja.

Zobacz, w poniższym przykładzie chcieliśmy przypisać człowiekowi dwie nogi. Niestety nasze czworonożne zwierzę tym samym także pozbyło się dwóch nóg.

const animal = {
    ears: 2,
    legs: 4,
};

const human = animal;
human.legs = 2;

console.log(animal.legs); // 2

Jeżeli chcesz teraz zgłębić ten temat, zachęcam Cię do odwiedzenia lekcji od kursjs.pl o płytkim i głębokim kopiowaniu obiektów.

 Słowo kluczowe this

Dowiedziałeś się już, że słowo this wskazuje na obiekt, w którego kontekście jest wykorzystywane. Przykładowo zapis this.color zastosowany wewnątrz obiektu pen będzie oznaczał „kolor przypisany do tego konkretnego obiektu pen”.

Są jednak sytuacje, w których this wskazuje na obiekt globalny window lub zmienia kontekst, np. w przypadku wykorzystania go w eventach JavaScript. Jak sobie z tym radzić, przeczytasz w lekcji „Trochę więcej o this” ponownie od kursjs.pl. Uwaga: ponieważ czytasz ten artykuł, zapewne to Twoje początki w JavaScripcie. Dlatego na razie radzę Ci nie przejmować się tematem this, gdyż w tej chwili będzie on trudny do zrozumienia. Wkrótce do niego wrócisz 🙂

 

Bez obiektów w JavaScripcie nie ma programowania – prędzej czy później trzeba oswoić się z ich strukturą i nauczyć się z nich korzystać. Już teraz zacznij używać ich tam, gdzie pozwolą Ci użytecznie grupować dane. Na przykład zamiast tworzyć kolejne zmienne dotyczące tego samego elementu (powiedzmy: textHeader, textDescription, textColor), wrzuć je do jednego obiektu text. Powodzenia!

Udostępnij ten artykuł:

Mentoring to efektywna nauka pod okiem doświadczonej osoby, która:

  • przekazuje Ci swoją wiedzę i nadzoruje Twoje postępy w zdobywaniu umiejętności,
  • uczy Cię dobrych praktyk i wyłapuje złe nawyki,
  • wspiera Twój rozwój i zwiększa zaangażowanie w naukę.