Object.create()

Wraz z rozwojem JavaScript dochodziły nowe sposoby na tworzenie obiektów. We wcześniejszych rozdziałach poznaliśmy tworzenie obiektów z wykorzystaniem konstruktora czy klasy, które pozwalały nam tworzyć obiekty o podobnych funkcjonalnościach. Charakterystyczną rzeczą było dla nich to, że miały wspólny prototyp.

Object.create()

Jednym z kolejnych sposobów na tworzenie obiektów jest użycie funkcji Object.create(), która służy do tworzenia nowych obiektów, które mają określony prototyp.

Jako pierwszy parametr przyjmuje ona obiekt, który stanie się prototypem nowo tworzonego obiektu:


const car = { //to będzie nasz prototyp
    drive() {
        console.log(this.name + " jadę");
    },
    refuel() {
        console.log(this.name + " tankuję");
    },
    stop() {
        console.log(this.name + " zatrzymuję się");
    }
}

const c1 = Object.create(car);
c1.name = "Samochód 1";
c1.drive(); //Samochód 1 jadę

const c2 = Object.create(car);
c2.name = "Samochód 2";
c2.drive(); //Samochód 2 jadę

console.log(c1);
console.log(c2);
object create

Drugim opcjonalnym parametrem Object.create() jest obiekt, w którym możemy ustawić właściwości nowo tworzonego obiektu:


const c1 = Object.create(car, {
    name: {
        value: 'Maluch'
    },
    km: {
        value: 0
    }
}

Ciekawostką tutaj jest to, że przy tworzeniu nowych obiektów za pomocą Object.create(), jako prototyp możemy podstawić nie tylko inne obiekty, ale także i null:


const ob1 = Object.create({}); //pusty obiekt jako prototyp
const ob2 = Object.create(null); //null jako prototyp

ob1.toString(); //[object Object]
ob2.toString(); //błąd

Jak wiemy z poprzednich rozdziałów, każdy obiekt ma właściwość __proto__, która wskazuje na prototyp. Nasz pusty obiekt także taką ma, a wskazuje ona na główny obiekt, w którym znajduje się między innymi metoda toString().

Jeżeli jednak podstawimy w to miejsce null, dostaniemy obiekt, który nie dziedziczy totalnie nic, co spowoduje, że nie będziemy w stanie nawet go wykorzystać w dodawaniu:


ob1 + "ala"; //[object Object]ala
ob2 + "ala"; //błąd, bo nie da się wywołać metody toString()

Deskryptory właściwości

W odróżnieniu od klasycznego ustawiania właściwości (np. c1.name = "Maluch"), przy zastosowaniu Object.create() mamy tutaj dodatkowe możliwości, ponieważ każdą tak tworzoną właściwość możemy opisać poprzez dodatkowe cechy:

enumerableczy wartość ma być widoczna przy pętlach iteracyjnych (np. for in, for of itp.) - np. właściwość length nie jest enumerable
writable czy zmienna może być zmieniana np. car.name = "Fiat"
configurable czy zmienna może mieć później zmieniane cechy - np. czy możemy później zmienić ją na nie enumerable, lub czy będziemy mogli ją usunąć poprzez delete)
valuewartość tej właściwości. Nie można tego używać wraz z get/set
gettak zwany getter - funkcja wywoływana gdy pobieramy daną wartość
settak zwany setter - funkcja wywoływana gdy ustawiamy daną wartość

const user = Object.create({}, {
    name : {
        value : "Marcin",
        configurable : true,
        writable : true
    },
    surname : {
        value : "Nowak",
        configurable : true,
        writable : true
    },
    status : {
        value : "single",
        configurable : true,
        writable : false
    }
});

user.status = "married";
console.log(user.status); //"single"

Podobnie jak w przypadku poprzedniego rozdziału możemy to też połączyć z geterami i seterami:


const user = Object.create({}, {
    name : {
        value : "Marcin",
        configurable : true,
        writable : true
    },
    surname : {
        value : "Nowak",
        configurable : true,
        writable : true
    },
    status : {
        value : "single",
        configurable : true,
        writable : false
    }
    fullName : {
        get : function() {
            return this.name + "  " + this.surname;
        },
        set : function(full) {
            const words = full.split(" ");
            this.name = words[0];
            this.surname = words[1];

        }
    }
});

user.fullName = "Karol Nowak";
console.log(user.name); //Karol
console.log(user.surname); //Nowak

defineProperty()

W powyższym kodzie stworzyliśmy obiekt, od razu przy tym ustawiając jego właściwości. Możemy też takie właściwości dodawać poza ciałem obiektu za pomocą defineProperty():


const ob = {
    _name : "piotr",
    _height : 180
};

Object.defineProperty(ob, "name", {
    set : function(newName) {
        this._name = newName;
    },
    get : function() {
        return this._name[0].toUpperCase() + this._name.substr(1).toLowerCase();
    }
});

Object.defineProperty(ob, "height", {
    get : function(height) {
        return this._height;
    },
    set : function(height) {
        this._height = height + 'cm';
    }
});

Object.defineProperty(ob, "gender", {
    writable : false,
    value : "male"
});

console.log(ob.name); //Marcin
ob.name = "grzegorz";
console.log(ob.name); //Grzegorz

ob.height = 180;
console.log(ob.height); //180cm

ob.gender = "woman"; //dalej "male" - nie możemy zmienić tej właściwości

Powyższe definiowanie właściwości możemy też połączyć z konstruktorem:


function Car(brand, color) {
    this.brand = brand;
    this.color = color;

    let _speed; //zmienna prywatna niedostępna z zewnątrz

    Object.defineProperty(this, "speed", {
        get: function() {
            return this._speed;
        },
        set: function(value) {
            this._speed = value;
            if (this._speed > 180) {
                this._speed = 180;
            }
        }
    });
}

const car = new Car("BMW", "Black");
car.speed = 160;
car.speed += 20;
car.speed += 20; // 180 a nie 200!
console.log(car.speed); //180km

Enumerable

Jak spojrzysz powyżej, jedną z właściwości jest enumerable, która oznacza, czy dana zmienna ma być listowana gdy wykonujemy pętlę na danym obiekcie. Sprawdźmy to na przykładzie:


const tab = ["Ala", "Ola", "Ula"];
console.dir(tab); //0: "Ala", 1: "Ola", 2: "Ula", "length" : 3

Powyżej mamy tablicę - czyli obiekt. Jak widzisz, gdy wypisujemy nasz obiekt za pomocą console.dir, wypisane są jego wszystkie klucze wraz z length.

Jeżeli jednak zrobimy pętlę po takim obiekcie, zostaną uwzględnione tylko klucze iterowalne:


const tab = ["Ala", "Ola", "Ula"];
for (const key in tab) {
    console.log(key); //0, 1, 2, ale nie length
}

Spróbujmy to zrobić na naszym obiekcie:


const ob = {
    name : "Marcin",
    age : "old"
}
ob.pet = "Szamson";

for (const key in ob) {
    console.log(key); //name, age, pet
}

Oznacza to, że tworząc w klasyczny sposób obiekty, wszystkie ich klucze są iterowalne.

Korzystając z funkcji Object.create możemy zdecydować, które właściwości naszego obiektu będą iterowalne:


const ob = Object.create({}, {
    name : {
        enumerable : true,
        value : "Marcin"
    },
    age : {
        enumerable : false,
        value : 200,
        writable : false
    }
});

for (const i in ob) {
    console.log(i); //name
}

Żeby sprawdzić jakie cechy ma dana właściwość, możemy posłużyć się funkcją Object.getOwnPropertyDescriptor():


Object.getOwnPropertyDescriptor(ob, "name");
//{value: "Marcin", writable: false, enumerable: true, configurable: false}

Jeżeli temat cię zainteresował polecam dodatkowe artykuły na ten temat:

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.

Menu