JavaScript 17.11
Obiekty
Notacje:
Dot Notation - po kropce do środka, notacja ta nie działa dla nazw ze spacjami, ksiazka.liczba stron da nam błąd
Bracket Notation - książka["liczba stron"] podajemy nazwę klucza w nawiasie, jeżeli są one niestandardowe np. mamy spacje, myślnik miedzy nazwami to potrzebujemy ich nazwy w cudzysłowach. Pozwala na używanie dynamicznych właściwości, jeśli chcemy użyć zmiennej w tym miejscu to musi być to notacja kwadratowa
jeśli chcemy dodać do obiektu kodem nowy klucz to możemy zrobić tak: ksiazka.jezyk='polski'. Jak wywołamy obiekt książka to pokaże sie nam również ten nowy klucz. Ten nowy klucz nie będzie na końcu, w przykładzie jest w środku bo obiekt nie jest uporządkowany tak jak tablica.
można dodać tez tak: ksiazka["jezyk"]="polski"
Usuwanie właściwości: delete ksiazka.ISBN10;
Sprawdzenie czy dana właściwość istnieje:
jak wywołamy console.log(ksiazka.isbn10) to nam zwróci undefined, aby zrobić to bezpiecznie (ksiazka?.ISBN10)
Potrzeba ifa dla bezpiecznego sprawdzenia
if("isbn10") in ksiazka ){ cosnole.log("właściwość istnieje"} else {console.log("nie istnieje")}
Bardziej złożone wyrażenia:
Jeśli tworzymy obiekt w pętli, chcemy je jakoś wyróżniać, albo kalkulować pewne klucze, to żeby skorzystać ze zmiennych które są wyżej, możemy użyć konkatenacji string lub stosować notacje z back tickami. Z pomocą back ticków ładnie się tworzy template, w zwykłej konkatenacji nie jest to takie ładne.
const ksiazka = {
tytul: "Pan Tadeusz",
autor: "Adam Mickiewicz",
rok: 1834,
liczbaStron: 500,
ISBN10: "1234567890",
kategoria: ["epopeja narodowa","rymowane"]
};
// Notacja kropkowa - prostsza i czytelniejsza
console.log("Tytuł:", ksiazka.tytul); // Wyświetli: Pan Tadeusz
console.log("Autor:", ksiazka.autor); // Wyświetli: Adam Mickiewicz
// Notacja nawiasowa - konieczna dla nazw ze spacjami i znakami specjalnymi
console.log("Liczba stron:", ksiazka["liczbaStron"]); // Wyświetli: 500
console.log("ISBN:", ksiazka["ISBNN10"]); // Wyświetli: 1234567890
// Notacja nawiasowa działa też dla zwykłych nazw
console.log("Rok (nawiasy):", ksiazka["rok"]); // Wyświetli: 1834
// Notacja kropkowa NIE działa dla nazw ze spacjami
// ksiazka.liczba stron // To spowodowałoby błąd!
//ksiazka.jezyk="polski"; // Dodawanie nowej właściwości
ksiazka["jezyk"]="polski"; // Dodawanie nowej właściwości
console.log(ksiazka); // Wyświetli: polski
// Usuwanie właściwości
delete ksiazka.ISBN10;
console.log(ksiazka); // Właściwość ISBN10 została usunięta
console.log(ksiazka?.ISBN10);
if("ISBN10" in ksiazka){
console.log("Właściwość ISBN10 istnieje");
} else {
console.log("Właściwość ISBN10 nie istnieje");
}Metody obiektu
Obiekty mogą przechowywać różne typy danych, stringi, liczby itd. Mogą przechowywać tablice. W szczególności możemy przechowywać funkcje w obiektach.
Funkcje są zdefiniowane jako właściwości obiektu. Czyli możemy tworzyć obiekt który już w środku ma dla siebie funkcje. THIS.
Jest obiekt z jednym kluczem i wartością ale w środku ma metody zdefiniowane funkcjami,
KALKULATOR, ma wynik: 0, dodaj:function(liczba) {this.wynik = this.wynik + liczba... inne funkcje są tak samo napisane.
Możemy użyć tych metod obiektu w sposób:
kalkulator.dodaj(5) dajemy argument 5 w środku, przykład z książką
const kalkulator = {
wynik: 0,
dodaj: function(liczba) {
this.wynik = this.wynik + liczba; // kalkulator.wynik += liczba;
return this.wynik;
},
odejmij: function(liczba) {
this.wynik = this.wynik - liczba;
return this.wynik;
},
pomnoz: function(liczba) {
this.wynik = this.wynik * liczba;
return this.wynik;
},
reset: function() {
this.wynik = 0;
return this.wynik;
}
};
// Używanie metod
console.log("Start:", kalkulator.wynik); // Wyświetli: 0
kalkulator.dodaj(5);
console.log("Po dodaniu 5:", kalkulator.wynik); // Wyświetli: 5
kalkulator.pomnoz(3);
console.log("Po pomnożeniu przez 3:", kalkulator.wynik); // Wyświetli: 15
kalkulator.odejmij(5);
console.log("Po odjęciu 5:", kalkulator.wynik); // Wyświetli: 10
kalkulator.reset();
console.log("Po resecie:", kalkulator.wynik); // Wyświetli: 0
const ksiazka = {
tytul: "Pan Tadeusz",
autor: "Adam Mickiewicz",
rok: 1834,
liczbaStron: 500,
ISBN10: "1234567890",
kategoria: ["epopeja narodowa","rymowane"],
changeISBN: function(newISBN) {
this.ISBN10 = newISBN;
console.log("obiekt po zmianie:", this);
return true;
}
};
if(ksiazka.changeISBN("0987654321")){
console.log("Zmiana powiodła się");
} else {
console.log("Zmiana nie powiodła się");const ksiazka = {
tytul: "Pan Tadeusz",
autor: "Adam Mickiewicz",
rok: 1834,
liczbaStron: 500,
ISBN10: "1234567890",
dodajNowyISBN: function(nowysISBN) {
this.ISBN10 = nowysISBN;
console.log(this);
},
}
ksiazka.dodajNowyISBN("5555555555");
console.log("ksiazka po zmianie:");
console.log(ksiazka);Metody w obiekcie tak jak funkcje mogą wywoływać inne metody, jesteśmy w stanie w naszym obiekcie zadeklarować jakieś metody i potem dać nowe metody (przykład z punktacja). Metody które przyjmują wartości jako argumenty w VS kodzie kolorują się jak klasy. Metoda nagroda wywołuje inne metody. Metody działają jak funkcje. Pamiętać o koncepcie blokowym, ze jak są zmienne w funkcji to one są tylko w tej funkcji, ale np. jesteśmy w stanie sięgnąć do metody która wykorzystuje ta zmienna.
const uzytkownik = {
imie: "Jan",
nazwisko: "Kowalski",
punkty: 0,
dodajPunkty: function(liczba) {
let zmienna=10;
this.punkty = this.punkty + liczba;
},
odejmijPunkty: function(liczba) {
this.punkty = this.punkty - liczba;
},
pokazInfo: function() {
return this.imie + " " + this.nazwisko + " ma " + this.punkty + " punktów";
},
nagroda: function() {
this.dodajPunkty(100);
return "Gratulacje! " + this.pokazInfo();
},
showVariable() {
return typeof zmienna;
}
};
// Metoda nagroda wywołuje inne metody
console.log(uzytkownik.nagroda());Gdzie się wykorzystuje takie konstrukty: metody razem z obiektem? tam gdzie chcemy mieć pewność, że nasze funkcje/metody korespondują razem z danymi, ze obiekt + jego metody funkcje są razem z nim przenoszone.
Jest sobie obiekt, np. konto bankowe, definiujemy w tym metody wpłata wypłata saldo, potem poza obiektem wystarczy wywołać w konsoli console.log(kontoBankowe.wplata)
const kontoBankowe = {
wlasciciel: "Anna Nowak",
saldo: 1000,
waluta: "PLN",
wplata: function(kwota) {
this.saldo = this.saldo + kwota;
return "Wpłacono " + kwota + " " + this.waluta + ". Nowe saldo: " + this.saldo;
},
wyplata: function(kwota) {
if (kwota > this.saldo) {
return "Niewystarczające środki. Dostępne: " + this.saldo + " " + this.waluta;
}
this.saldo = this.saldo - kwota;
return "Wypłacono " + kwota + " " + this.waluta + ". Nowe saldo: " + this.saldo;
},
sprawdzSaldo: function() {
return this.wlasciciel + " - saldo: " + this.saldo + " " + this.waluta;
}
};
Tak długo jak nasz obiekt istnieje możemy mieć na nim nieskończenie wiele razy wykonywać operacje.
Metody Iteracji po Obiekcie
Obiekty maja metody do iteracji po obiekcie:
Object Keys - tablica kluczy. Podajemy Object.keys(w nawiasie obiektu do którego się odwołujemy) wszystkie właściwości obiektu
Object Values - tablica wartości, Object.values(nazwaobiektu)
Object Entries - cały obiekt, dwuwymiarowa tablica, każdy jej element ma klucz i wartość, najsłabsza do iterowania
Zwracają one tablice, wykonujemy je na konstrukcie object nie na obiekcie. Skoro są tablice - wykorzystujemy metody tablicowe. Możemy wykorzystać np. wartość lenght.
Iteracja za pomocą for...in skoro mam klucze to mogę wyświetlić wartość posługując się notacja nawiasowa.
Możemy iterować się zwykłym for to wystarczy np. klucze.lenght - wyliczyć co je pożądaną rzeczą do pętli zewnętrznie
const limit =klucze.lenght;
Te metody są przydatne do iteracji po obiektach - użyć for, for each, bleble metody tablicowe na zwróconych tablicach. Dynamiczne, zwłaszcza jeśli elementy się zmieniają, dodajemy usuwamy wartości itd.
Jeśli długość byłaby zero to znaczy ze tablica jest pusta.
const limit =klucze.lenght; if (object.keys(produkt).lenght === 0) { cosnole.log("obiekt jest pusty");} else { console.log("obiekt nie jest pusty");}
Możemy Filtrować obiekty - sklep internetowy, liczba produktów, ustawienia obiekt który jest konfiguracyjny i chcemy konkretne rzeczy z takich obiektów wyciągnąć to to jest metoda.
const ustawieniaBool = Object.entries(ustawienia).filter(function(wpis){ }
const produkt = {
nazwa: "Smartfon",
marka: "Samsung",
cena: 2500,
dostepny: true
};
console.log("Produkt:", produkt);
// Object.keys - tablica kluczy
const klucze = Object.keys(produkt);
console.log("Klucze:", klucze);
// Object.values - tablica wartości
const wartosci = Object.values(produkt);
console.log("Wartości:", wartosci);
// Wyświetli: ["Smartfon", "Samsung", 2500, true]
// Object.entries - tablica par [klucz, wartość]
const wpisy = Object.entries(produkt);
console.log("Wpisy:", wpisy);
// Iteracja za pomocą for...in
console.log("Iteracja za pomocą for...in:");
for (let klucz in produkt) {
console.log(klucz + ": " + produkt[klucz]);
}
const limit= klucze.length;
if(Object.keys(produkt).length === 0){
console.log("Obiekt jest pusty");
} else {
console.log("Obiekt nie jest pusty");
}
const ustawienia = {
motyw: "ciemny",
jezyk: "pl",
powiadomienia: true,
autoZapis: true,
odstep: 2
};
const ustawieniaBool = Object.entries(ustawienia).filter(function(wpis) {
const wartość = wpis[1];
return typeof wartość === "boolean";
});
console.log("Ustawienia boolean:", ustawieniaBool);Destructuring
Destruktor obiektu, wyciąga masowo wartości z obiektu
const {imie, wiek, zawod} = osoba;
console.log(imie, wiek, zawod)
Jeśli potrzeba zmiany nazwy takiej właściwości to jesteśmy w stanie zmienić nazwę podczas tego destrukturyzowania
const { nazwisko: nazwiskoOsoby, miasto:miejsceZamieszaknia} = osoba;
console.log(naziwskoOsoby, miejsceZamieszkania)
Możemy przypisywać wartości domyślne
rozmiarCzionki =14, autozapis=true
Spread operator
pozwala na rozłożenie właściwości obiektu. Używamy go do kopiowania, łączenia obiektów, dodawania nowych właściwości. Tworzy płytką kopie tzn kopiuje wartości na pierwszym poziomie, a zagnieżdżone obiekty są kopiowane jako referencje. Przydatny jeśli chcemy stworzyć nowy obiekt na bazie istniejącego z jakimiś modyfikacjami, nie musimy ręcznie kopiować ale możemy to zrobić tym spread a potem dopisać, zmieniać itd. Można łączyć, wyciągać itd to jest FUNDAMENT nowoczesnego programowania obiektowego
const laptop = {
... bazoweProdukty (tu się odwołuje do wcześniejszego obiektu)
nazwa: "Laptop", promocja: true}
const laptopWPromocji = {
...laptop
}
możemy też łączyć
const osoba = {
imie: "Maria",
nazwisko: "Kowalska",
wiek: 30,
zawod: "programista",
miasto: "Warszawa"
};
// Tradycyjny sposób przypisania
const imie1 = osoba.imie;
const wiek1 = osoba.wiek;
// Destructuring - krócej i czytelniej
const { imie, wiek, zawod } = osoba;
console.log(imie, wiek, zawod); // Wyświetli: Maria 30 programista
// Destructuring z aliasami
const { nazwisko: nazwiskoOsoby, miasto: miejsceZamieszkania } = osoba;
console.log(nazwiskoOsoby, miejsceZamieszkania); // Wyświetli: Kowalska Warszawa
// Destructuring z wartościami domyślnymi
const { port, host, protokol = "http" } = konfiguracja;
console.log("Konfiguracja:", protokol, host, port);
// Wyświetli: Konfiguracja: http localhost 3000
// protokol użył wartości domyślnej bo nie było w obiekcie
// Praktyczny przykład - ustawienia z domyślnymi wartościami
const ustawieniaUzytkownika = {
jezyk: "pl",
motyw: "jasny"
};
const { jezyk, motyw, rozmiarCzcionki = 14, autoZapis = true } = ustawieniaUzytkownika;
console.log("Język:", jezyk); // Wyświetli: pl
console.log("Motyw:", motyw); // Wyświetli: jasny
console.log("Rozmiar czcionki:", rozmiarCzcionki); // Wyświetli: 14 (domyślna)
console.log("Auto-zapis:", autoZapis); // Wyświetli: true (domyślna)
const bazoweProduktu = {
kategoria: "elektronika",
dostepny: true,
rabat: 0
};
// Tworzenie nowego obiektu ze spread
const laptop = {
...bazoweProduktu,
nazwa: "Laptop Dell",
cena: 3000
};
console.log("Laptop:", laptop);
// Wyświetli: {kategoria: "elektronika", dostepny: true, rabat: 0,
// nazwa: "Laptop Dell", cena: 3000}
// Kopiowanie i nadpisywanie właściwości
const laptopWPromocji = {
...laptop,
rabat: 15,
promocja: true
};
console.log("Laptop w promocji:", laptopWPromocji);
// rabat został nadpisany na 15
// Łączenie obiektów
const podstawoweDane = {
id: 1,
nazwa: "Produkt"
};
const szczegoly = {
cena: 100,
opis: "Opis produktu"
};
const pelnyProdukt = {
...podstawoweDane,
...szczegoly
};
console.log("Pełny produkt:", pelnyProdukt);
// Wyświetli: {id: 1, nazwa: "Produkt", cena: 100, opis: "Opis produktu"}Są metody które nie tylko zwracają tablice
metoda reduce - redukuje tablice, zmienia ja w zadany przez nas sposób. przyjmuje ona dwa argumenty - funkcję którą wywołujemy wewnątrz metody reduce i opcjonalną wartość początkową. Ta funkcja callbackowa jest wykonywana dla każdego elementu tablicy
Mamy tablice z numerami
numbers = [10,20,30,40,50];
dla każdego elementu wykonuje sum + number startowe jest 0 wiec jak zaczynamy od pierwszego elementu to mamy 0+10 potem 10+20, 30+30, 60+40 itd.
wywołujemy na niej funkcje która zwraca sumę, return sum + number),0), jesteśmy w stanie przekształcić tablicę w obiekt, mapujemy produkt id = produkt wiec zmodyfikowaliśmy w związku tym tablice. To jest podobne jak for each ale wynik może nie musi być tablicą np jak mamy logowanie do tablicy jakies pary gps, na podstawie tych współrzędnych chcemy obliczyć pokonaną drogę, to taka funkcja reduce jesteśmy w stanie to wykonać. Ma opcjonalny początkowy parametr, wiec możemy sobie to wyfiltrować, pominąć wskazując odpowiedni element. Reduce działa w lewo, od pierwszego elementu i przesuwa się do następnego. Jeśli potrzebujemy aby nasze dane były przetwarzane w drugą stronę - reduceRight
// Tablica liczb do zsumowania
const numbers = [10, 20, 30, 40, 50];
// Metoda reduce przyjmuje funkcję callback i wartość początkową
// Parametr sum to akumulator - przechowuje wynik
// Parametr number to bieżący element tablicy
const total = numbers.reduce(function(sum, number) {
// W każdej iteracji dodajemy bieżącą liczbę do sumy
// Zwracana wartość staje się akumulatorem w następnej iteracji
return sum + number;
}, 0); // 0 to wartość początkowa akumulatora
console.log("Suma liczb:", total); // Wyświetli: Suma liczb: 150
const products = [
{ id: "p1", name: "Laptop", price: 3000 },
{ id: "p2", name: "Mysz", price: 50 },
{ id: "p3", name: "Klawiatura", price: 150 }
];
const totalPrice = products.reduce(function(sum, product) {
return sum + product.price;
}, 0);
const productMap = products.reduce(function(map, product) {
// Dla każdego produktu tworzymy wpis w obiekcie
// Używamy ID jako klucza
map[product.id] = product;
// Zwracamy zaktualizowany obiekt map
return map;
}, {});
console.log("Mapa produktów:", productMap);
const words = ["JavaScript", "jest", "super"];
// Używamy reduce do połączenia od początku
const sentenceReduce = words.reduce(function(sentence, word) {
return sentence + " " + word;
}, "Zdanie:");
console.log(sentenceReduce); // Wyświetli: Zdanie: JavaScript jest super
const sentenceReduceRight = words.reduceRight(function(sentence, word) {
return sentence + " " + word;
}, "Zdanie:");
console.log(sentenceReduceRight); // Wyświetli: Zdanie: super jest JavaScriptMetoda mutująca - nie tworzy kopii tablicy tylko działa bezpośrednio na tej tablicy na której wykonujemy sort i potem zwraca do posortowanej tablicy.
Odwołanie do pracy domowej o sklepie:
Sort
Przy porównywaniu stringów jest pewna pułapka- case sensivity, małe litery są ważniejsze niż wielkie litery. Sortowanie alfabetycznie - komputer nie rozumie, każda litera ma swój kod i a ma najmniejszy kod. Sortowanie alfabetycznie nie działa ładnie.
Jak weźmiemy sobie tablice z numbers,
sortowanie asc w górę, desc malejąco,
numberAsc = number.slice();
numberDesc = number.slice()
jeżeli wywołujemy sort bez argumentów to JavaScript konwertuje wszystkie elementy na string i sortuje wg kodu ascii, wiec to znaczy ze liczby mogą być posortowane jak string, musimy powiedzieć jak ma wyglądać to sortowanie, jak tego nie zrobimy to porównują się te wartości jako string.
Więc używamy funkcji porównującej która ma własną logikę porównywania. Przyjmuje dwa argumenty - czyli dwie wartości z tablicy, a i b, funkcja zwraca wartość porównująca te liczby, b odejmujemy od a, jeśli wartość jest ujemna to a będzie przed b, jeśli dodatnia to b przed a, jeśli jest zerem to zostaje nie zmieniona.
W przypadku obiektów, właściwości obiektów tu dopiero sort zaczyna błyszczeć, bo skoro mamy obiekty w tablicy, możemy odwołać się do konkretnych elementów i je porównywać w funkcji sortującej, to nikt nie zabroni nam dowolnych operacji wykonywać.
podnieść do uppercase - bo dla samej nazwy, skoro będziemy wyświetlać tablice to nie ma znaczenia, a jak podniesiemy upper case to pozbędziemy się problemu że jedna nazwa jest mała litera a druga duża
Sort jest względnie dobrze zoptymalizowana, funkcja porównująca jest wywoływana wielokrotnie dla tablicy n elementowej, nawet może być n*n, dlatego funkcja porównująca musi być szybka i efektywna.
Wykonywać operacje przed sortowaniem, przechować w tablicy i dopiero robić sort.
localCompare - uwzględnia lokalne litery, różne języki mają różne metody sortowania. dla polskiego języka l i ł powinny być po sobie a w tablicy liter to one nie są obok siebie, a metoda pozwala na sortowanie stringów zgodnie z lokalnym językiem
Dobre praktyki:
- funkcja sortująca tak prosta jak to możliwe
- powinna być czyta - zadanych efektów ubocznych, nic więcej nie powinno się dziać poza wynikiem na sortowaniu
- nie powinna polegać na stanach zewnętrznych gdzie jeszcze powołujemy zmienna globalna
- powinna być spójna- dla tych samych wejść zwraca ten sam wynik
- nazwa funkcji powinna być czytelna
Domyślne sort jest do bani dla stringow, jak nie użyjemy localCompare to z ł nie będzie obok l itd, dla liczb domyślny sort tez jest do bani bo nie porównuje liczb a stringi. Żeby te sortowania wyszły dobrze powinniśmy uzywac localCompare a najlepiej podnosić nasze wartości np do upper case do jednego standardu.
const shopItems = [
{ itemName: "Laptop", price: 3000 },
{ itemName: "Mysz", price: 50 },
{ itemName: "Klawiatura", price: 150 },
{ itemName: "Monitor", price: 800 },
{ itemName: "Słuchawki", price: 200 }
];
function sortProductsByNames() {
shopItems.sort((a, b) => a.itemName.localeCompare(b.itemName));
console.table(shopItems);
}
sortProductsByNames();
// Tablica liczb do posortowania
const numbers = [50, 10, 80, 30, 25];
const numbers2 = [2,11,7, 50, 10, 80, 30, 25];
numbers2.sort();
console.log("Domyślne sortowanie:", numbers2);
const numbersAsc = numbers.slice();
// Sortujemy rosnąco
// Funkcja porównująca odejmuje b od a
// Jeśli a < b, wynik jest ujemny więc a przed b
numbersAsc.sort(function(a, b) {
return a - b;
});
console.log("Liczby rosnąco:", numbersAsc); // Wyświetli: [10, 25, 30, 50, 80]
// Kopiujemy ponownie dla sortowania malejącego
const numbersDesc = numbers.slice();
// Sortujemy malejąco - odwracamy kolejność odejmowania
numbersDesc.sort(function(a, b) {
return b - a;
});
console.log("Liczby malejąco:", numbersDesc); // Wyświetli: [80, 50, 30, 25, 10]Zaawansowane metody obiektowe
Pozwalają na operacje na obiektach bez nowych algorytmów itd.
Płytka kopia - taka, która nie kopiuje zagnieżdżonych elementów, jeśli coś jest tablicą itd. to wartości nie są kopiowane, a kopiowane są tylko referencje. Takie zagnieżdżone obiekty są dla oryginału i obiektu, ich modyfikacja ma wpływ na kopie i oryginał.
Głęboka kopia - spread object.assign nie działa, trzeba użyć rekurencji.
object assign - kopiuje właściwości z source do target, target jest mutowany.
jeżeli nie mamy w naszym obiekcie paru wartości, to możemy napisać taka ładna szybka funkcje, bierze obiekt, zapisuje go do json, a potem parsuje tego jsona do tego obiektu.
żeby robić głębokie kopie które maja wartości które w ten sposób można konwertować, to w takim obiekcie nie może być paru typów danych, w ten sposób jesteśmy w stanie skopiować obiekt pod warunkiem ze nie ma w nim funkcji, nie ma dat, nie ma nim undefined bo to wartości które nie konwertują się do JSON.
W praktyce - pełna kopia całego obiektu który jest rozbudowany, zawiera funkcje, struktur danych itd to albo zewnętrza biblioteka która takie kopie obiektów robi lub używać konstruktora obiektu który tworzy pusty obiekt ale z taka cała struktura i można te instancje obiektowe tworzyć
// Obiekt docelowy (target)
const target = {
a: 1,
b: 2
};
// Obiekt źródłowy (source)
const source = {
b: 3, // Nadpisze wartość w target
c: 4, // Nowa właściwość
d:{ e: 5 }
};
// Object.assign kopiuje właściwości z source do target
// UWAGA: target jest mutowany!
const result = Object.assign(target, source);
console.log("Wynik:", result);
console.log("Target po przypisaniu:", target);
console.log("Source:", source);
result.d.e = 10;
console.log("Target po zmianie zagnieżdżonej właściwości:", target);
console.log("Source po zmianie zagnieżdżonej właściwości:", source);
source.c = 20;
console.log("Target po zmianie właściwości w source:", target);
// Dla głębokiej kopii trzeba użyć innych technik
function deepCopySimple(obj) {
// Prosty sposób dla prostych struktur
// NIE działa dla funkcji, dat, undefined
return JSON.parse(JSON.stringify(obj));
}
const nowyObiekt = deepCopySimple(source);
console.log("Nowy obiekt (głęboka kopia):", nowyObiekt);
nowyObiekt.d.e = 50;
console.log("Nowy obiekt po zmianie zagnieżdżonej właściwości:", nowyObiekt);
console.log("Source po zmianie w nowym obiekcie:", source);
// nie ma w nim funkcji, nie ma w nim date, nie ma w nim undefinedObject.freeze, Object.seal
Te metody pozwalają na kontrolowanie tego czy obiekt może być modyfikowany i w jaki sposób
Object.freeze - całkowite zamrożenie, które sprawia ze obiekt staje się inmutable nie możliwa jest jego zmiana, nie możemy dodawać nowych właściwości, usuwać, nie możemy zmieniać wartości istniejących wartości, wszystkie właściwości staja się niemodyfikowalne a obiekt staje się obiektem tylko do odczytu. Przydatne jak mamy konfiguracje, dane cos co nie może być zmienione. Freeze odnosi się do płytkiej kopii obiektu, ale właściwości wewnętrznego obiektu może być zmieniona. Jeśli chcemy sprawdzić czy jest to obiekt i czy cały jest zablokowany, testujemy czy on nie ma zagnieżdżonych obiektów możemy poprzez object.keys zobaczyć obiekt obiektu, do każdego klucza obiektu sprawdziliśmy typeof, a skoro sprawdzamy na każdym typeof to możemy dorzucić warunek, ze jeżeli typeof jest object, to osobno robimy freeze na elemencie który jest obiektem.
Object.seal - zapieczętowany obiekt, który nie może mieć dodawanych ani usuwany właściwości, obiekt którego nie chcemy aby właściwości były usunięte bo np. usunęliśmy cos przez przypadek, wartości mogą się zmieniać ale klucze nie. Modele danych, te pola maja takie być i nie można dodawać nowych pół to ten object.seal jest użyteczny.
prevent extensions - do obiektu nie można dodawać nowych właściwości, ale te które istnieją mogą być modyfikowane i usuwane, jeśli nie chcemy by się obiekt rozrastał to można tego użyć
metody do sprawdzenia zamrożenia/zapieczętowania
Object.isFrozen
Object.isSeal
Potrzeba lepszego freeze - albo sie iterujemy jak w przykładzie powyżej
// Obiekt do zamrożenia
const config = {
apiKey: "SECRET_KEY",
timeout: 5000,
maxRetries: 3,
test: {
a: 1,
b: 2
}
};
Object.freeze(config);
config.timeout = 10000; // Ta zmiana nie zostanie zastosowana, ponieważ obiekt jest zamrożony
console.log(config.timeout); // 5000
config.newProperty = "newValue"; // Ta właściwość nie zostanie dodana
console.log(config.newProperty); // undefined
// Próba usunięcia właściwości
delete config.apiKey; // Ta operacja nie powiedzie się
config.test.a = 42; // Właściwość wewnętrznego obiektu może być zmieniona
console.log(config.test.a); // 42
const klucz = Object.keys(config);
console.log(klucz); // ["apiKey", "timeout", "maxRetries", "test"]
klucz.forEach(element => {
console.log(`${element}: ${config[element]}, type: ${typeof config[element]}`);
});
// Obiekt reprezentujący użytkownika
const user = {
id: 123,
name: "Jan Kowalski",
email: "[email protected]"
};
Object.seal(user);user.name = "Anna Nowak"; // Ta zmiana zostanie zastosowana
console.log(user.name); // "Anna Nowak"
delete user.email; // Ta operacja nie powiedzie się
console.log(user.email); // "[email protected]"
// sprawdzanie czy obiekty są zamrożone lub zapieczętowane
console.log(Object.isFrozen(config)); // true
console.log(Object.isSealed(user)); // truePrototypy
Każdy obiekt może mieć inny obiekt jako swój prototyp, od niego dziedziczy właściwości, pozwala na ponowne używanie kodu.
Prototyp - obiekt z którego wszystkie elementy dziedziczny inny obiekt. Tworzymy prototyp, obiekt a potem mówimy zrób nowy obiekt na wzór tego pierwszego.
const person = {
name: "Jan",
greet: function() {
return "Witaj, " + this.name;
}
};
console.log(person);
// Każdy obiekt ma prototyp - dostępny przez __proto__
// lub Object.getPrototypeOf()
const proto = Object.getPrototypeOf(person);
console.log(typeof person.hasOwnProperty === "function");Protptype chain sprawdzanie właściwości, wartości czy obiekt lub jego rodzic rodzic rodzica itd go zawierają. Pozwala na definiowanie metod które są definiowane raz w prototypie i są używane przez wszystkie obiekty dziedziczące. Im dłuższy ten chain tym dłużej trwa znalezienie takiej właściwości która nie jest bezpośrednio zdefiniowana w obiekcie.
Shadowing jeśli obiekt ma nazwę o tej samej nazwie co nazwa w prototypie to ta własna właściwość jest zasłaniania.
Klasy w ES6 to taki dodatek, syntactic sugar, żeby programiści przechodzący z innych języków dobrze się poczuli w javascripcie.
Konstruktory
Sposób na tworzenie dużej ilości obiektów, wielu obiektów które maja tak samo wyglądać, korzystać z tych samych metod a jednocześnie mieć różne dane.
jeśli funkcja jest wywoływana z new to js robi nowy obiekt, prototyp, zwraca z this itd.
Literalnie robiliśmy const person={ imie: naziwsko itd}
Aby zrobić wzorzec - obiekt wzrocowy:
function Person(name, age) {
this.name = name;
this.age = age;
}
nowe obiekty za pomocą słowa new
const p1= new Person("jan",30);
const p2= new Person("kasia"32);
function Person(name, age) {
// 'this' odnosi się do nowo tworzonego obiektu
// Definiujemy właściwości instancji
this.name = name;
this.age = age;
}
// Tworzymy instancje używając operatora 'new'
const person1 = new Person("Jan Kowalski", 30);
const person2 = new Person("Anna Nowak", 25);
console.log(person1.name); // "Jan Kowalski"
console.log(person2.age); // 25
console.log(person1)// Konstruktor definiuje właściwości instancji
function Car(brand, model, year) {
this.brand = brand;
this.model = model;
this.year = year;
this.mileage = 0;
}
// Metody definiujemy w prototype
// Wszystkie instancje współdzielą te same metody
Car.prototype.drive = function(distance) {
this.mileage = this.mileage + distance;
return "Przejechano " + distance + " km";
};
Car.prototype.getAge = function() {
const currentYear = 2024;
return currentYear - this.year;
};
Car.prototype.getInfo = function() {
return this.brand + " " + this.model + " (" + this.year + ")";
};
// Tworzymy instancje
const car1 = new Car("Toyota", "Corolla", 2020);
const car2 = new Car("Honda", "Civic", 2018);
console.log(car2);
console.log(car1.getInfo()); // "Toyota Corolla (2020)"
console.log(car2.drive(150)); // "Przejechano 150 km"
// Możemy dodać metodę do prototype później
// Stanie się dostępna dla wszystkich instancji
Car.prototype.honk = function() {
return this.brand + " trąbi: Beep beep!";
};
console.log(car1.honk()); // "Toyota trąbi: Beep beep!"
console.log(car2.honk()); // "Honda trąbi: Beep beep!"Dziedziczenie
// Konstruktor bazowy - Animal
function Animal(name) {
this.name = name;
this.energy = 100;
}
// Metody Animal w prototype
Animal.prototype.eat = function() {
this.energy = this.energy + 10;
return this.name + " je i ma " + this.energy + " energii";
};
Animal.prototype.sleep = function() {
this.energy = this.energy + 20;
return this.name + " śpi";
};
// Konstruktor pochodny - Dog
function Dog(name, breed) {
Animal.call(this, name); // Wywołanie konstruktora bazowego
this.breed = breed; // Nowa właściwość specyficzna dla Dog
}
// Ustawienie dziedziczenia prototypowego
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const animal = new Animal("Zwierzę");
const dog = new Dog("Burek", "Owczarek");
console.log(animal.eat());
console.log(dog.eat());
console.log(dog.breed);
console.log(dog);