Obiekty JavaScript od podstaw - referencje, this i kopiowanie

Cztery figurki szturmowców LEGO, obok siebie, z napisem "Metody klonowania obiektów w JavaScript".

Autor

Jacek Zając

Opublikowano

4 lis 2026

Spis treści

Gdy aplikacja zaczyna przechowywać użytkowników, produkty, ustawienia albo wyniki formularza, same zmienne szybko przestają wystarczać. Obiekty JS pozwalają połączyć powiązane dane i operacje w jedną czytelną strukturę, dlatego są podstawą niemal każdego większego programu. Pokażę, jak je tworzyć, odczytywać, kopiować, porównywać i wykorzystywać bez typowych pułapek związanych z referencjami, metodami oraz prototypami.

Obiekt łączy dane z zachowaniem i porządkuje logikę aplikacji

  • Właściwości przechowują dane, a metody wykonują operacje związane z tymi danymi.
  • Do odczytu używa się najczęściej notacji kropkowej, natomiast nawiasy kwadratowe obsługują dynamiczne nazwy kluczy.
  • Przypisanie obiektu do drugiej zmiennej kopiuje referencję, a nie całą strukturę.
  • Spread i Object.assign() tworzą kopię płytką, więc zagnieżdżone obiekty nadal mogą być współdzielone.
  • Prototypy i klasy pozwalają współdzielić metody między wieloma instancjami.

Diagram przedstawia relacje między obiektami, funkcjami i prototypami w JavaScript. Obiekty dziedziczą właściwości i metody poprzez łańcuch prototypów.

Jak myśleć o obiekcie w JavaScript

Najprościej wyobrazić sobie obiekt jako pojemnik z parami klucz i wartość. Klucz opisuje, czego szukamy, a wartość może być tekstem, liczbą, tablicą, innym obiektem albo funkcją. Funkcja zapisana jako właściwość staje się metodą, czyli zachowaniem przypisanym do danego obiektu.

const użytkownik = {
  imię: "Anna",
  wiek: 29,
  aktywny: true,
  przedstawSię() {
    return `Mam na imię ${this.imię}.`;
  }
};

W tym przykładzie imię, wiek i aktywny to właściwości, a przedstawSię() to metoda. Takie połączenie jest praktyczne, bo kod dotyczący użytkownika znajduje się w jednym miejscu, zamiast być rozrzucony po wielu niezależnych funkcjach.

Trzeba też pamiętać, że obiekt nie jest tym samym co JSON. JSON jest formatem tekstowym służącym do wymiany danych, natomiast obiekt JavaScript może zawierać funkcje, wartości typu undefined, symbole i inne elementy, których JSON nie obsługuje bezpośrednio.

Obiekty, tablice i wartości proste

Tablica również jest obiektem, ale została zaprojektowana przede wszystkim do przechowywania uporządkowanej sekwencji elementów. Obiekt lepiej pasuje do danych opisowych, na przykład pojedynczego produktu, konfiguracji albo profilu klienta.

Struktura Najlepsze zastosowanie Przykład
Obiekt Dane opisane nazwami właściwości { nazwa: "Laptop", cena: 3499 }
Tablica Uporządkowana lista elementów ["HTML", "CSS", "JavaScript"]
Map Dynamiczne pary klucz-wartość i częste operacje na kluczach new Map()

W praktyce nie próbuję upychać wszystkiego do zwykłego obiektu. Gdy klucze są dynamiczne, ich liczba często się zmienia, a program regularnie je dodaje i usuwa, Map bywa czytelniejszym wyborem. Do typowego modelu danych, konfiguracji czy odpowiedzi z API zwykły obiekt nadal sprawdza się najlepiej.

Tworzenie i odczytywanie właściwości bez zgadywania

Najczęściej tworzę obiekt za pomocą literału, czyli nawiasów klamrowych. Można też użyć konstruktora Object, ale w codziennym kodzie literał jest krótszy i łatwiejszy do odczytania.

const produkt = {
  nazwa: "Klawiatura",
  cena: 249,
  dostępny: true
};

const pustyObiekt = {};

Właściwość odczytamy przez kropkę albo nawiasy kwadratowe. Notacja kropkowa jest wygodniejsza, gdy nazwa klucza jest znana podczas pisania kodu. Nawiasy są potrzebne, gdy nazwa pochodzi ze zmiennej lub zawiera znaki, których nie można użyć w zwykłym identyfikatorze.

console.log(produkt.nazwa);
console.log(produkt["cena"]);

const wybranaWłaściwość = "dostępny";
console.log(produkt[wybranaWłaściwość]);

produkt.magazyn = 18;
produkt["kolor produktu"] = "czarny";

Jeśli odwołasz się do nieistniejącej właściwości, otrzymasz undefined. Przy danych z formularza lub API bezpieczniej użyć optional chaining, czyli operatora ?., który zatrzymuje odczyt, gdy wcześniejszy fragment jest pusty.

const zamówienie = {
  klient: {
    adres: {
      miasto: "Gdańsk"
    }
  }
};

console.log(zamówienie.klient?.adres?.miasto);
console.log(zamówienie.klient?.telefon);

const liczbaSztuk = zamówienie.liczbaSztuk ?? 1;

Operator ?? ustawia wartość domyślną tylko wtedy, gdy po lewej stronie znajduje się null albo undefined. To ważne rozróżnienie, bo 0, false i pusty tekst mogą być poprawnymi wartościami, których nie chcemy przypadkiem zastąpić.

Destrukturyzacja poprawia czytelność

Gdy obiekt ma wiele właściwości, nie musisz za każdym razem pisać jego pełnej nazwy. Destrukturyzacja wyciąga wybrane dane do osobnych zmiennych, a wartości domyślne chronią kod przed brakującymi polami.

const osoba = {
  imię: "Marek",
  stanowisko: "Frontend developer"
};

const { imię, stanowisko, doświadczenie = 0 } = osoba;

console.log(`${imię} pracuje jako ${stanowisko}.`);
console.log(`Lata doświadczenia: ${doświadczenie}`);

Możesz także zmienić nazwę lokalnej zmiennej. Przydaje się to wtedy, gdy kilka obiektów ma właściwość o tej samej nazwie albo gdy nazwa z API nie pasuje do konwencji używanej w projekcie.

const { imię: imięUżytkownika } = osoba;
console.log(imięUżytkownika);

Metody, this i operowanie na danych

Metoda ma sens wtedy, gdy wykonuje operację naturalnie należącą do obiektu. W poniższym przykładzie koszyk nie tylko przechowuje produkty, ale potrafi też obliczyć ich łączną wartość.

const koszyk = {
  produkty: [
    { nazwa: "Mysz", cena: 120, liczba: 2 },
    { nazwa: "Podkładka", cena: 60, liczba: 1 }
  ],
  suma() {
    return this.produkty.reduce(
      (wynik, produkt) => wynik + produkt.cena * produkt.liczba,
      0
    );
  }
};

console.log(koszyk.suma());

W metodzie this wskazuje obiekt, na którym wykonano wywołanie, czyli w tym przypadku koszyk. To jedna z rzeczy, które początkujący często rozumieją dopiero po kilku błędach. Liczy się sposób wywołania funkcji, a nie tylko miejsce, w którym została zapisana.

const profil = {
  nazwa: "Konto firmowe",
  pokażNazwę() {
    return this.nazwa;
  }
};

const funkcja = profil.pokażNazwę;

console.log(profil.pokażNazwę()); 
console.log(funkcja());

Drugie wywołanie może zwrócić błąd albo undefined, ponieważ metoda została odłączona od obiektu. Jeśli trzeba przekazać ją dalej, użyj bind() albo funkcji opakowującej. Funkcje strzałkowe nie tworzą własnego this, dlatego zwykle nie są dobrym wyborem dla metod, które mają korzystać z właściwości obiektu.

Dodawanie i usuwanie danych

Właściwości można dopisywać i zmieniać po utworzeniu obiektu. Usunięcie wykonuje operator delete, ale stosuję go ostrożnie, szczególnie w danych przekazywanych między warstwami aplikacji. Często bezpieczniej zostawić pole z wartością null albo utworzyć nowy obiekt bez niepotrzebnej właściwości.

const ustawienia = {
  motyw: "ciemny",
  język: "pl"
};

ustawienia.animacje = true;
ustawienia.język = "en";

delete ustawienia.animacje;

Jeżeli wartość obiektu zawiera tekst, osobne operacje na ciągach znaków również powinny być wydzielone od logiki modelu. Przy bardziej rozbudowanych zmianach tekstu przyda się opis metody replace() w JavaScript, zwłaszcza gdy zwykła zamiana nie wystarcza i potrzebujesz wyrażenia regularnego.

Kopiowanie, porównywanie i iterowanie po obiektach

To miejsce, w którym najczęściej pojawia się nieintuicyjne zachowanie. Obiekty są przechowywane i przekazywane przez referencję, więc przypisanie do nowej zmiennej nie tworzy niezależnej kopii.

const pierwszy = { licznik: 1 };
const drugi = pierwszy;

drugi.licznik = 5;

console.log(pierwszy.licznik);

Wynik to 5, ponieważ obie zmienne wskazują na ten sam obiekt. Dotyczy to także porównywania. Dwa osobne obiekty z identyczną zawartością nie są równe za pomocą ===, bo mają różne referencje.

const a = { status: "gotowy" };
const b = { status: "gotowy" };

console.log(a === b); // false

Kopia płytka i głęboka

Najwygodniejszą kopię płytką tworzy operator spread. Kopiuje on właściwości pierwszego poziomu, ale zagnieżdżone obiekty i tablice nadal pozostają współdzielone.

const konfiguracja = {
  tryb: "ciemny",
  widok: {
    rozmiarCzcionki: 16
  }
};

const kopia = { ...konfiguracja };
kopia.tryb = "jasny";

console.log(konfiguracja.tryb); // "ciemny"

Jeżeli zmienisz kopia.widok.rozmiarCzcionki, zmiana dotknie także oryginału. Dla prostych danych zagnieżdżonych można użyć structuredClone(), ale trzeba pamiętać o jego ograniczeniach, na przykład funkcje nie zostaną skopiowane jako działające funkcje. Nie stosuj automatycznie JSON.stringify i JSON.parse, bo taki sposób gubi między innymi undefined, funkcje, symbole oraz typy takie jak Date.

Wyszukiwanie i iterowanie

Do sprawdzania własnych właściwości dobrze pasują Object.keys(), Object.values() i Object.entries(). Zwracają odpowiednio klucze, wartości albo pary klucz-wartość, co ułatwia budowanie tabel, walidację danych i transformacje.

const ceny = {
  html: 0,
  css: 20,
  javascript: 40
};

for (const [technologia, cena] of Object.entries(ceny)) {
  console.log(`${technologia}: ${cena} zł`);
}

Do wykrywania własnej właściwości można użyć Object.hasOwn(). Operator in sprawdza także właściwości odziedziczone z prototypu, dlatego nie zawsze odpowiada na pytanie, czy klucz został zapisany bezpośrednio w danym obiekcie.

Prototypy, klasy i obiekty w większej aplikacji

Każdy obiekt może mieć prototyp, czyli inny obiekt, z którego pobiera brakujące właściwości i metody. Dzięki temu wiele instancji może współdzielić jedną metodę zamiast przechowywać osobną kopię funkcji w każdym egzemplarzu.

class Konto {
  constructor(właściciel, saldo = 0) {
    this.właściciel = właściciel;
    this.saldo = saldo;
  }

  wpłać(kwota) {
    this.saldo += kwota;
  }

  pokażSaldo() {
    return `${this.właściciel}: ${this.saldo} zł`;
  }
}

const kontoAnny = new Konto("Anna", 100);
const kontoPiotra = new Konto("Piotr", 250);

kontoAnny.wpłać(50);
console.log(kontoAnny.pokażSaldo());

Składnia class wygląda podobnie do klas znanych z innych języków, ale pod spodem nadal korzysta z mechanizmu prototypów. Metody zdefiniowane w klasie są współdzielone przez instancje, co jest rozsądnym rozwiązaniem, gdy tworzysz wiele obiektów tego samego rodzaju.

Nie każda struktura wymaga klasy. Dla prostego obiektu z kilkoma polami literał bywa lepszy, bo nie dodaje dodatkowej warstwy abstrakcji. Klasy mają sens przy powtarzalnym modelu i zachowaniu, natomiast zwykłe obiekty dobrze pasują do jednorazowych konfiguracji, danych formularza i odpowiedzi z API.

W większym projekcie obiekty są też podstawą komunikacji między komponentami, usługami i warstwą danych. Gdy aplikacja zaczyna korzystać z frameworka, znajomość pracy z właściwościami, metodami oraz niemutowalnymi kopiami bardzo ułatwia wybór i naukę takich narzędzi jak framework Angular.

Przeczytaj również: Frameworki JavaScript - Jak wybrać idealny dla Twojego projektu?

Najczęstsze błędy w pracy z obiektami

  • Mylenie kopii z referencją i przypadkowa zmiana danych w kilku miejscach naraz.
  • Używanie for...in bez sprawdzenia, czy właściwość należy bezpośrednio do obiektu.
  • Traktowanie this jak stałego wskaźnika, mimo że jego wartość zależy od sposobu wywołania.
  • Wrzucanie do jednego obiektu danych, walidacji, komunikacji z serwerem i obsługi interfejsu.
  • Stosowanie klasy tam, gdzie wystarczyłby prosty literał albo funkcja tworząca obiekt.

Sam zwracam szczególną uwagę na ostatni problem. Rozbudowany obiekt może początkowo wyglądać jak wygodne centrum logiki, ale po czasie trudniej go testować i modyfikować. Lepiej trzymać dane oraz operacje blisko siebie, lecz nie mieszać odpowiedzialności, które należą do różnych części aplikacji.

Najkrótsza droga do pewnego kodu z obiektami

Zacznij od prostego literału i naucz się swobodnie odczytywać oraz zmieniać jego właściwości. Potem przećwicz destrukturyzację, metody, operator spread i iterowanie przez Object.entries(). Dopiero gdy pojawi się wiele podobnych instancji, sięgaj po klasy i świadomie korzystaj z prototypów.

Najważniejsza praktyczna zasada brzmi prosto: sprawdzaj, czy potrzebujesz danych, zachowania, czy obu rzeczy naraz. Taki wybór prowadzi do czytelniejszego kodu niż automatyczne używanie jednej techniki w każdym miejscu.

FAQ - Najczęstsze pytania

Przypisanie kopiuje referencję, a nie całą strukturę. Dlatego zmiana właściwości przez drugą zmienną modyfikuje także oryginalny obiekt. Operator spread i Object.assign() tworzą kopię płytką, więc zagnieżdżone obiekty i tablice nadal mogą być współdzielone.

Notacja kropkowa sprawdza się, gdy nazwa właściwości jest znana w kodzie, na przykład produkt.nazwa. Nawiasy kwadratowe są potrzebne przy dynamicznych nazwach pobieranych ze zmiennej oraz przy kluczach zawierających nietypowe znaki lub spacje. Do bezpiecznego odczytu zagnieżdżonych danych można użyć ?., a ?? ustawia wartość domyślną tylko dla null i undefined.

Wartość this zależy od sposobu wywołania funkcji. profil.pokażNazwę() wywołuje metodę w kontekście obiektu, ale przypisanie jej do osobnej zmiennej odłącza ją od tego obiektu. Przy przekazywaniu metody dalej można użyć bind() albo funkcji opakowującej, natomiast funkcje strzałkowe nie tworzą własnego this.

Zwykły obiekt pasuje do konfiguracji, danych formularza, odpowiedzi z API i prostych modeli. Map bywa czytelniejsza przy dynamicznych kluczach oraz częstym dodawaniu i usuwaniu wpisów. Klasa ma sens, gdy tworzysz wiele podobnych instancji współdzielących metody, ponieważ korzysta z prototypów.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi:

referencje kopiowanie destrukturyzacja prototypy this

Udostępnij artykuł

Jacek Zając

Jacek Zając

Nazywam się Jacek Zając i od sześciu lat zajmuję się programowaniem webowym. Moja przygoda z tym obszarem zaczęła się od chęci stworzenia własnej strony internetowej, co szybko przerodziło się w pasję do tworzenia aplikacji i rozwiązań, które mogą ułatwiać życie innym. Lubię dzielić się wiedzą, szczególnie w zakresie podstaw programowania oraz budowania responsywnych aplikacji internetowych. W moich tekstach staram się w przystępny sposób wyjaśniać złożone zagadnienia, porównując różne podejścia i technologie, a także zwracając uwagę na aktualne trendy w branży. Zawsze dbam o to, aby informacje, które przekazuję, były rzetelne i zrozumiałe, a także aby były zgodne z najnowszymi standardami. Moim celem jest nie tylko przekazywanie wiedzy, ale także inspirowanie innych do rozwijania swoich umiejętności w programowaniu.

Napisz komentarz