Dostęp do właściwości obiektu
Każdy obiekt posiada zestaw właściwości, które możemy odczytywać lub modyfikować. Możemy to robić za pomocą kropki albo nawiasu kwadratowego:
let person = {
name: "Jan",
lastName: "Kowalski",
age: 30
};
console.log(person.name); // wyświetli "Jan"
console.log(person['lastName']); // wyświetli "Kowalski"
Gdy odpytujemy się o nieistniejącą właściwość, JavaScript nie generuje błędu, lecz zwraca undefined
, co oznacza, że właściwość ta nie została zdefiniowana:
console.log(person.proffesion); // undefined
Jednakże, gdy spróbujemy odwołać się do właściwości nieistniejącej właściwości, np. chcemy sprawdzić miasto
w nieistniejącym adresie
:
console.log(person.adress.city); // TypeError: Cannot read properties of undefined (reading 'city')
W powyższym przypadku, JavaScript zgłosi błąd TypeError
, ponieważ próbujemy odwołać się do właściwości miasto
obiektu undefined
(ponieważ osoba.adres
jest undefined
).
Aby uniknąć tego typu błędów możemy skorzystać ze składni ?. (operator warunkowy) jeżeli któryś element składni ma wartość null lub undefined całe wyrażenie zwraca undefined
console.log(person.adress?.city); // undefined, bez błędu
Dziedziczenie
Obiekty w JavaScript mogą dziedziczyć właściwości z innych obiektów. Oznacza to, że jeśli właściwość nie jest znaleziona w obiekcie, silnik JavaScript szuka jej w prototypie rodzica:
let person = {
name: "Jan",
lastName: "Kowalski",
age: 30
};
let secondPerson = Object.create(person);
secondPerson.name = "Anna";
console.log(secondPerson.lastName); // "Kowalski" - dziedziczone z obiektu person
Iteracja właściwości
const person = {
name: "Jan",
lastName: "Kowalski",
age: 30
};
for (const key in person) {
console.log(`${key}: ${person[key]}`);
}
W tym przykładzie, pętla for...in
przechodzi przez wszystkie właściwości obiektu person
, umożliwiając wykonanie operacji na każdej z nich, np. wyświetlenie ich nazw i wartości.
Użycie Object.keys() do iteracji:
Object.keys(person).forEach(key => {
console.log(`${key}: ${person[key]}`);
});
Object.keys(person)
zwraca tablicę zawierającą wszystkie klucze (czyli nazwy właściwości) obiektu person
Kopiowanie właściwości obiektów
Rozszerzanie obiektów umożliwia wzbogacenie istniejących struktur o nowe funkcje i atrybuty bez ingerencji w ich pierwotny kod. Dzięki operacji na dwóch lub więcej obiektach, możemy elastycznie łączyć ich właściwości, zapewniając łatwość aktualizacji i rozszerzalność kodu. To kluczowe rozwiązanie dla zachowania czystości i modularności projektu.
Object.assign()
Metoda ta przyjmuje jako pierwszy argument obiekt docelowy, do którego będą kopiowane właściwości, a jako kolejne argumenty – obiekty źródłowe, z których właściwości będą kopiowane. Jeśli obiekty źródłowe mają właściwości o tych samych nazwach, właściwości z ostatnich obiektów w kolejności argumentów nadpiszą właściwości w obiekcie docelowym. Przykład:
const personA = { name: "Anna", age: 25 };
const personB = { profession: "Developer" };
Object.assign(personA, personB);
// personA now contains: { name: "Anna", age: 25, profession: "Developer" }
Składnia spread (...
)
Innym sposobem jest użycie składni spread (...
), która pozwala na bardziej zwięzłe dodawanie właściwości z jednego obiektu do drugiego. Przykład z użyciem składni spread:
const personC = { name: "Jan" };
const personExtends = { age: 30, profession: "Inżynier" };
const object = { ...personC, ...personExtends };
Głębokość kopiowania:
Obie techniki wykonują płytką kopię właściwości. To oznacza, że jeśli kopiowana właściwość jest obiektem lub tablicą, to kopiowany jest tylko jej referencyjny adres, a nie sam niezależny obiekt. Dlatego zmiany w zagnieżdżonych obiektach źródłowych po skopiowaniu wpłyną na wszystkie obiekty, które odnoszą się do tego samego zagnieżdżonego obiektu. Przykład:
const projectTemplate = {
status: 'Not started',
teamMembers: {
leader: 'Alice',
developer: 'Bob'
},
tasks: ['Setup', 'Development', 'Testing']
};
const newProject = { ...projectTemplate };
console.log(projectTemplate.teamMembers.developer); //Bob
newProject.teamMembers.developer = 'Charlie';
console.log(projectTemplate.teamMembers.developer); //Charlie
W tym przykładzie, po skopiowaniu projectTemplate
do newProject
za pomocą operatora rozproszenia (...
) i zmianie developer
w newProject
, zauważamy, że zmiana ta wpłynęła również na oryginalny szablon projectTemplate
. Dzieje się tak, ponieważ skopiowaliśmy płytko obiekt, co oznacza, że zagnieżdżone obiekty, takie jak teamMembers
, nie zostały skopiowane; skopiowane zostały tylko ich referencje.
Rozwiązanie problemu: głęboka kopia
Aby uniknąć niechcianego wpływu na oryginalny obiekt, musimy zrobić głęboką kopię. Jednak JavaScript nie oferuje wbudowanej, łatwej metody na głębokie kopiowanie obiektów. Jednym ze sposobów (choć nie zawsze zalecanym ze względu na potencjalne problemy z wydajnością i kwestie bezpieczeństwa) jest użycie serializacji i deserializacji JSON:
const deepCopyProject = JSON.parse(JSON.stringify(projectTemplate));
deepCopyProject.teamMembers.developer = 'Dave';
console.log(projectTemplate.teamMembers.developer); // Outputs: 'Charlie'
console.log(deepCopyProject.teamMembers.developer); // Outputs: 'Dave'
W tym przypadku, deepCopyProject
jest całkowicie niezależną kopią projectTemplate
, więc zmiany w deepCopyProject
nie wpływają na oryginał. Należy jednak pamiętać, że metoda z użyciem JSON nie zadziała z obiektami zawierającymi metody, cykliczne referencje, specjalne typy danych JavaScript (takie jak Map
, Set
, Date
, funkcje itd.)
Metody obiektów
toString()
Metoda toString()
jest częścią standardowego prototypu Object
. I jest automatycznie wywoływana przez JavaScript w kontekstach, gdzie jest wymagana reprezentacja tekstowa (stringowa) obiektu. Dzieje się to w różnych scenariuszach, zarówno jawnie, jak i niejawnie, co sprawia, że jest to jedna z fundamentalnych metod w języku JavaScript. Oto kilka przykładów sytuacji, w których JavaScript wywoła metodę toString()
:
Konkatenacja z łańcuchami znaków: Kiedy obiekt jest łączony z łańcuchem znaków przy użyciu operatora +
, JavaScript automatycznie konwertuje obiekt na łańcuch znaków przy użyciu metody toString()
.
const obj = { key: "value" };
console.log("My object is " + obj); // "My object is [object Object]"
Użycie w szablonach łańcuchów (template strings): Kiedy obiekt jest osadzony w szablonie łańcucha znaków, metoda toString()
jest wywoływana, aby uzyskać jego reprezentację tekstową.
console.log(`Object: ${obj}`); // "Object: [object Object]"
Każdy typ danych (np. Array, Date, itp.) ma swoją własną implementację metody toString()
, która dostosowuje format zwracanego łańcucha znaków do typu danych. Na przykład, wywołanie toString()
na tablicy zwróci łańcuch znaków zawierający elementy tablicy rozdzielone przecinkami:
const array = [1, 2, 3];
console.log(array.toString()); // Wypisze: "1,2,3"
Metoda toString()
może być również nadpisana w niestandardowych obiektach, aby zwracać bardziej specyficzne lub użyteczne reprezentacje obiektów w postaci łańcucha znaków, co jest przydatne na przykład do debugowania:
const person = {
name: 'Alice',
toString: function() {
return `Name: ${this.name}`;
}
};
console.log(person.toString()); // Wypisze: "Name: Alice"
valueOf()
Metoda valueOf()
w JavaScript jest używana do zwracania wartości prymitywnej obiektu. Jest ona częścią standardowego prototypu Object
i może być nadpisana w niestandardowych obiektach, aby dostosować zachowanie obiektu przy próbach konwersji na typ prymitywny. Podobnie jak toString()
, valueOf()
jest wywoływana automatycznie w kontekstach, które wymagają wartości prymitywnej obiektu, chociaż jej głównym zastosowaniem jest konwersja na typy liczbowe.
toJson()
W JavaScript nie istnieje wbudowana metoda toJson()
jako część standardowego API obiektów. Metoda toJSON()
jest wywoływana, gdy obiekt jest serializowany do formatu JSON, na przykład za pomocą JSON.stringify()
. Nie jest to metoda, która automatycznie konwertuje obiekt na JSON; raczej jest to sposób na dostosowanie, jak obiekt powinien być reprezentowany przed jego konwersją do formatu JSON. Jeśli obiekt posiada metodę toJSON()
, JSON.stringify()
wywoła tę metodę i użyje jej wyniku jako wartości do serializacji, zamiast bezpośredniej serializacji obiektu.
const person = {
name: "Alice",
age: 25,
toJSON: function() {
return { name: this.name }; // Zwraca tylko własność 'name' przy serializacji
}
};
const jsonPerson = JSON.stringify(person);
console.log(jsonPerson); // Wypisze: '{"name":"Alice"}'
W tym przykładzie, obiekt person
zawiera metodę toJSON()
, która zwraca nowy obiekt zawierający tylko własność name
. Gdy JSON.stringify()
serializuje person
, wynikowy łańcuch JSON zawiera tylko te dane, które są zwracane przez toJSON()
, pomijając pozostałe właściwości obiektu, takie jak age
.
Nadpisywanie metody toJSON()
jest przydatne, gdy chcemy kontrolować, jakie dane obiektu powinny być uwzględnione w serializacji JSON, na przykład do celów bezpieczeństwa, wydajności lub po prostu dla uproszczenia wynikowego formatu JSON. Pozwala to na wykluczenie wrażliwych lub zbędnych danych, a także na przekształcanie danych przed ich wysłaniem lub zapisaniem.