Obiekty i this

W Javascript prawie wszystko jest obiektem, a my w naszych skryptach nieraz i nie dwa odwołujemy się do obiektów. Na przykład window, Array, Math, Date itp. to typowe obiekty, które posiadają swoje metody i właściwości:


const tab = [1,2,3];
tab.length //widzisz tą kropkę? Przez nią odwołujemy się do metod lub właściwości obiektu - w tym przypadku obiektu typu Array

const text = "Ala ma kota";
console.log(  text.charAt(0) ); //text - typ prosty został na chwilę zamieniony na obiekt (po czym od razu po wykonaniu akcji wrócił do typu prostego)

window.height - właściwość height obiektu window

Math.floor(21.3); //metoda obiektu Math

document.cookie //właściwość cookie obiektu document

(new Data()).getFullYear(); //metoda getFullYear() obiektu Date;

itp...

button.innerHTML //właściwość innerHTML obiektu button

Poza dostępem do gotowych obiektów, możemy tworzyć też swoje własne. No ale właściwie po co?
Pamiętam jak sam uczyłem się obiektów. Po co mi to. Mam zmienne, mam tablice. Przecież nic więcej mi nie potrzeba.

Obiekty układają nam dane w pewne "zamknięte" pudełka. Popatrz na obiekty które cię otaczają. Weźmy na przykład psa. Taki obiekt ma brązową sierść, cztery łapy, brązowe oczy, jest ładny (bo jest). To jego właściwości. Potrafi też biegać, jeść, szczekać. To jego czynności - w naszym języku zwane funkcje. A wszystko to upakowane w jeden obiekt - zwany psem. Dzięki temu jeżeli chcemy zrobić coś z psem - np. go umyć czu zabrać do weterynarza, mamy dostęp do tych wszystkich rzeczy pod postacią pojedynczego obiektu.

obiekt pies

Może nie jest to idealny przykład. Przykład bliższy naszej działce to chociażby pierwszy lepszy element na stronie. Ma właściwości określające jego zawartość (np. tekst), szerokość, wysokość, pozycję na stronie, itp. Ale też ma metody, czyli funkcje które może wykonać - np. focus czy blur, które go zaznaczą lub odznaczą gdy zostaną odpalane.

Dzięki obiektom o wiele łatwiej, logiczniej i przyjemniej na naszych danych działać. Zresztą przekonasz się o tym za chwilę.

Tworzenie pojedynczego obiektu

Obiekty możemy podzielić na pojedyncze instancje (pojedyncze byty), oraz grupy obiektów o podobnych właściwościach (np. tablice, linki, buttony, stringi itp).

Na początku zajmijmy się pojedynczymi obiektami.
Aby utworzyć nowy pojedynczy obiekt możemy skorzystamy z poniższej konstrukcji:


const myObj = {
    name  : "Pies",
    speed : 1000,
    print : function() {
        console.log("Lubię walczyć ze złem");
    }
}

Stworzyliśmy nasz obiekt o nazwie myObj za pomocą prostej pary klamer (tak zwany literał).
Obiekt taki to zbiór par "klucz" : "wartość".
Nasz obiekt posiada dwie właściwości - name i height, oraz jedną funkcję (zwaną też metodą), która wypisuje jakiś tekst.
Zauważ, że przy definiowaniu takiego obiektu właściwości rozdzielamy przecinkiem, a zamiast znaku równości stosujemy dwukropek.

Żeby teraz się do nich odwołać napiszemy:


myObj.name //Pies
myObj.speed //1000

myObj.print(); //"Lubię walczyć ze złem"

Nie wydaje ci się to podobne do już używanych rzeczy?


window.innerWidth //właściwość innerWidth obiektu window
[1,2,3,4].length //właściwość length obiektu Array

Math.max(1,2,3) //metoda max() obiektu Math
window.alert("ok"); //metoda alert() obiektu window

console.log("lorem"); //metoda log() obiektu console

Można powiedzieć, że nasz obiekt myObj to taka jakby tablica dla swoich właściwości. To co odróżnia go od tablicy to to, że w tablicach by odwołać się do jakiejś wartości musimy wiedzieć dokładnie, na którym miejscu (indeksie) ta wartość się znajduje. W obiektach odwołujemy się po nazwie właściwości (po kluczu), więc takiego miejsca znać nie musimy.


const tab = ["Mercedes", "czerwony", 150, function() {...}];
console.log("Marka: ", tab[0]);
console.log("Kolor: ", tab[2]);


const car = {
    brand : "Mercedes",
    color : "czerwony",
    speed : 150,
    print : function() {
        console.log(car.brand + ' koloru ' + car.color);
    }
}

console.log("Marka: ", car.brand);
console.log("Kolor: ", car.speed);

Obiekty mogą być oczywiście bardziej rozbudowane, bo każda właściwość może wskazywać na kolejne obiekty. Im dalej w las tym będziesz się stykać z coraz bardziej rozbudowanymi obiektami:


const myObj = {
    name : "Marcin",
    height : 184,

    pet : {
        name : "Szamson",
        color : "brown",
        speed : 1000,

        collar : {
            color : "red",
            length : "25cm"
        }
    }
}

myObj.name //Marcin
myObj.pet.name //Szamson
myObj.pet.collar.color //red

Dodawanie nowych właściwości

Do stworzonego wcześniej obiektu możemy dodawać metody i właściwości nie tylko w jego ciele (jak powyżej), ale także poza nim:


const car = {
    brand : "Mercedes",
    color : "czerwony",
    speed : 150,
    print : function() {
        console.log(car.brand + ' koloru ' + car.color);
    }
}

car.doors = 4;
car.wheels = 4;
car.drive = function() {
    console.log('Jadę sobie żwawo!');
}

car.print();
car.drive();

this

Aby odwołać się do danego obiektu z wnętrza którejś z jego metod (czyli z wnętrza jego funkcji) możemy tak jak powyżej użyć nazwy obiektu:


const car = {
    brand : "Mercedes",
    color : "czerwony",
    speed : 150,
    print : function() {
        console.log("Marka: ", car.brand);
        console.log("Kolor: ", car.color);
        console.log("Szybkość: ", car.speed);
    }
}

car.print();

Nie jest to jednak zalecana metoda, ponieważ ogranicza używanie naszego kodu. Dokładniej o co chodzi dowiesz się w dalszej części tego działu. Na razie uznajmy, że powyższe odwołanie się po nazwie obiektu nie jest tym dobrym.

Zamiast konkretnej nazwy obiektu, o wiele lepszym rozwiązaniem jest zastosować słowo kluczowe this, które wskazuje na obiekt, który odpalił daną metodę (czyli w większości przypadków obiekt, w którym ta funkcja jest zawarta):


const car = {
    brand : "Mercedes",
    color : "czerwony",
    speed : 150,
    print : function() {
        console.log("Marka: ", this.brand);
        console.log("Kolor: ", this.color);
        console.log("Szybkość: ", this.speed);
    }
}

car.drive = function() {
    console.log(this.brand + " sobie jadę!");
}

car.print();
car.drive();

Usuwanie właściwości i metod

Aby usunąć właściwość lub metodę obiektu, skorzystamy z operatora delete:


const car = {
    brand : "Mercedes",
    color : "czerwony"
    speed : 150,
    print : function() {
        console.log(this.brand + ' koloru ' + this.color );
    }
}

console.log(car.color); //czerwony
delete car.color;
console.log(car.color); //undefined

W Javascript nie musimy usuwać całych obiektów. Jeżeli na dany obiekt nie będzie wskazywała żadna zmienna, jego automatycznym usunięciem zajmie się tak zwany Garbage Collector. Jeżeli chciałbyś usunąć cały obiekt, nie musisz tego robić. Wystarczy, że taką zmienną ustawisz na null, co zerwie referencje danej zmiennej z obiektem.

Różne odwołania

Do właściwości i metod obiektów możemy odwoływać się na dwa sposoby - poprzez notację kropki i korzystając z kwadratowych nawiasów:


const ob = {
    name : "Marcin",
    pet : "pies",
    pisz : function() { ... }
}

ob.name
ob.pet
ob.pisz();

ob["name"]
ob["pet"]
ob["pisz"]();

Druga metoda - może rzadziej stosowana - czasami bardzo się przydaje. Musisz wiedzieć, że w nazwach kluczy mogą wystąpić znaki niedopuszczalne w nazwach zmiennych. Oznacza to, że nie możemy w takich sytuacjach zastosować notacji z kropką, bo wyrzuci to błąd składni:


const calendar = {
    "2018-11-11" : {
        name : "Narodowe Święto Niepodległości"
    },
    "2018-12-24" : [
        name : "Wigilia Bożego Narodzenia"
    ],
    ...
}

calendar.2018-11-11 //oczywisty błąd bo odejmujemy od 2018 coś tam
calendar["2018-11-11"] //tutaj ok

Odwołania się do właściwości poprzez kwadratowe nawiasy były przez nas stosowane już w przypadku tablic - które także są obiektami.


const tab = ["kot", "pies", "chomik super ninja"];


tab.0 //błędne odwołanie
tab[0] //kot
tab["0"] //kot


tab[2] //chomik super ninja
tab["2"] //chomik super ninja
tab.2.length //błędne odwołanie
tab["2"].length //18


tab.length //3 - bo length to właściwość o nazwie "length"
tab["length"] //3

Przy podawaniu indeksów w tablicach podajemy je jako numery. Do tego się przyzwyczailiśmy. Tak samo jak przy obiektach (bo tablice nimi są) moglibyśmy podawać je jako string, ale nie musimy tego robić, bo JavaScript automatycznie dokonuje tutaj konwersji.

Pętla for in

W Javascript istnieje pętla for in, która służy do iterowania po obiektach. Jej zastosowanie ma następującą postać:


const car = {
    brand : "Mercedes",
    color : "czerwony",
    speed : 150,
    print : function() {
        console.log("Marka: ", this.brand);
        console.log("Kolor: ", this.color);
        console.log("Szybkość: ", this.speed);
    }
}

for (const i in car) {
    console.log(i); //brand, color, speed, print
}

Jak widzisz robiąc iteracje pod zmienną i podstawiane są kolejne klucze. By pobrać ich wartość zastosujemy kwadratowe nawiasy:


for (const i in car) {
    console.log("Klucz: ", i);
    console.log("Wartość: ", car[i]);
}

Czasami zdarza się, że będziemy mieli dane w postaci obiektu, po którym będzie trzeba iterować:


const calendar = {
    "2018-11-11" : {
        name : "Narodowe Święto Niepodległości"
    },
    "2018-12-24" : [
        name : "Wigilia Bożego Narodzenia"
    ],
    ...
}

for (const key in calendar) {
    console.log("Święta na dzień: " + key);
    console.log(calendar[key]);
}
Mimo że tablice też są obiektami, nie stosuj dla nich powyższej pętli, bo może to przynieść niepożądane efekty. Do iterowania po tablicach stosuj przeznaczone do tego pętle - for, for of i forEach)

Przykład użycia - grupowanie elementów w tablicy

Wyobraź sobie, że masz tablicę, w której chciałbyś zsumować podobne elementy. Jednym z możliwych podejść do rozwiązania tego zadania jest zastosowanie kwadratowych nawiasów.


//tablica w której chcemy zliczyć występowanie danych elementów
const tab = [
    "Ala ma kota",
    3,
    "Ania lubi czekoladki",
    "Ala ma kota",
    2,
    "Piesek Rysiek",
    "Piesek Rysiek",
    2,
    "Ania lubi czekoladki",
    "Ania lubi czekoladki"
];


const ob = {};
tab.forEach(function(el) {
    if (!ob.hasOwnProperty(el)) { //jeżeli właściwości o takiej nazwie nasz obiekt nie ma to ją tworzymy
        ob[el] = 0;
    }
    ob[el]++;
})

console.log(ob);
Obiekt z właściwościami

Zauważ jakie właściwości ma nasz obiekt. Notacją z kropką nie byłbyś w stanie się do nich odwołać. Pozostaje notacja z nawiasami.


ob.2.length //błąd
ob["2"].length //2
ob["3"].length //1
ob["Ala ma kota"].length //2
ob["Ania lubi czekoladki"] //3

Dodatkowe materiały

W Internecie jest wiele fajnych materiałów na poniższe tematy.
Możliwe, że ten film lepiej wyjaśni ci poniższe zagadnienia, lub np. ta strona.

Trening czyni mistrza

Poniżej zamieszczam kilka zadań, które w ramach ćwiczenia możesz wykonać:

  1. Stwórz 2 obiekty produktów prod1 i prod2, gdzie każdy z nich będzie miał właściwości:

    - name - typu string,
    - price - typu number,
    - weight - typu number

    1) Wypisz oba obiekty w konsoli.
    2) Wypisz w konsoli teksty:
    "Produkt numer jeden to: aaa"
    "Produkt numer dwa to: bbb"
    "Produkty kosztują razem: ccc"
    "Produkty ważą razem: ddd"
    Gdzie pod zmienne podstaw odpowiednie dane.
    
                    const product1 = {
                        name : "Książka 1",
                        price : 20,
                        weight : 0.5
                    }
    
                    const product2 = {
                        name : "Kubek",
                        price : 25,
                        weight : 0.2
                    }
    
                    console.log(product1);
                    console.log(product2);
                    console.log("Produkt numer jeden to: " + product1.name);
    console.log("Produkt numer dwa to: " + product2.name);
    console.log("Produkty kosztują razem: " + (product1.price + product2.price));
    console.log("Produkty ważą razem: " + (product1.weight + product2.weight));
  2. Stwórz obiekt currentUser. Obiekt niech ma właściwości:
    - name - typu string,
    - surname - typu string,
    - email - typu string,
    - www - typu string,
    - userType,

    oraz metodę show(), która wypisze wszystkie te rzeczy w konsoli.

    Wypisywany tekst powinien być ładnie sformatowany np.:
    
                console.log("Imię: ", ....);
                console.log("Nazwisko: ", ....);
            
    Wywołaj metodę tego obiektu.
    
                    const currentUser = {
                        name : "Marcin",
                        surname : "Nowak",
                        email : "marcin.nowak@gmail.com",
                        www : "marcin.nowak.pl",
                        userType : "editor",
    
                        show : function() {
                            console.log("Imię: " + this.name);
                            console.log("Nazwisko: " + this.surname);
                            console.log("Email: " + this.email);
                            console.log("www: " + this.www);
                            console.log("Typ użytkownika: " + this.userType);
                        }
                    }
    
                    currentUser.show();
                
  3. Stwórz obiekt book, który będzie miał właściwości:
    - title - typu string,
    - author - typu string,
    - pageCount - typu string,
    - publisher - typu string

    oraz metodę: showDetails(), która zrobi pętlę po tym obiekcie wypisując wszystkie jego klucze oraz ich wartości. Metoda ta nie powinna wypisywać tylko funkcji print. Jak sprawdzić czy dany klucz zawiera funkcję?
    
                    const book = {
                        title : "Thorgal",
                        author : "Grzegorz Rosiński",
                        pageCount : 48,
                        publisher : "Egmont Polska Sp. z o.o."
    
                        showDetails: function() {
                            for (const i in this) {
                                if (typeof this[i] !== "function") {
                                    console.log("Klucz: " + i);
                                    console.log("Wartość: " + this[i]);
                                }
                            }
                        }
                    }
                
  4. Poniżej masz tablice użytkowników.

    
                const users = [
                    //id name surname email age value
                    [  1, "Shauna", "Bradnocke", "sbradnocke0@altervista.org", 20, 108.28 ],
                    [  2, "Mela", "Redman", "mredman1@nps.gov", 24, 267.37 ],
                    [  3, "Othelia", "Lemon", "olemon2@slashdot.org", 15, 748.06 ],
                    [  4, "Meier", "Cockell", "mcockell3@icio.us", 32, 1951.64 ],
                    [  5, "Shellysheldon", "Gronowe", "sgronowe4@cnbc.com", 16, 1040.54 ],
                    [  6, "Francisca", "Tofanini", "ftofanini5@gnu.org", 21, 1544.08 ],
                    [  7, "Cliff", "Underwood", "cunderwood6@addtoany.com", 10, 451.21 ],
                    [  8, "Caron", "Falshaw", "cfalshaw7@hugedomains.com", 27, 1968.72 ],
                    [  9, "Anitra", "Warters", "awarters8@intel.com", 12, 380.68 ],
                    [ 10, "Caitrin", "Baudrey", "cbaudrey9@ihg.com", 13, 1385.44 ]
                ]
                

    Jest to bardzo zła forma danych - czemu?

    Napisz funkcję fixData(tab), której zadaniem będzie zrobić pętlę po przekazanej tablicy i wygenerować nową tablicę z obiektami, gdzie każdy z nich będzie miał klucze: id, name, surname, email, age, money.

    
                function fixData(tab) {
                    const newTab = [];
    
                    for (const el of tab) {
                        const ob = {
                            id : el[0],
                            name : el[1],
                            surname : el[2],
                            email : el[3],
                            age : el[4],
                            money : el[5]
                        }
                        newTab.push(ob);
                    }
    
                    return newTab;
                }
    
    
                //lub za pomocą Array.map
    
    
                function fixData(tab) {
                    return tab.map(function(el) {
                        return {
                            id : el[0],
                            name : el[1],
                            surname : el[2],
                            email : el[3],
                            age : el[4],
                            money : el[5]
                        }
                    });
                }
                
  5. Po napisaniu funkcji z poprzedniego zadania użyj jej do stworzenia nowej zmiennej fixTab zawierającą poprawioną tablicą. Wypisz ją w konsoli, a następnie działając na tej zmiennej stwórz nowy obiekt summary z właściwościami:

    - money : która będzie zawierała zsumowane pieniądze
    - age : która będzie zawierała zsumowany wiek użytkowników
    - averageAge : która będzie zawierała średni wiek użytkowników
    
                const fixTab = fixData(users);
    
                console.log(fixTab);
    
                let money = 0;
                let age = 0;
    
                for (const el of fixTab) {
                    money += el.money;
                    age += el.age;
                }
    
                const summary = {
                    money : money,
                    age : age,
                    averageAge : age / fixTab.length
                }