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.
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);
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
}
}
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:
enumerable | czy 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) |
value | wartość tej właściwości. Nie można tego używać wraz z get/set |
get | tak zwany getter - funkcja wywoływana gdy pobieramy daną wartość |
set | tak 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); //Piotr
ob.name = "Grzegorz";
console.log(ob.name); //Grzegorz
ob.height = 160;
console.log(ob.height); //160cm
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:
- http://arqex.com/967/javascript-properties-enumerable-writable-configurable
- https://addyosmani.com/resources/essentialjsdesignpatterns/book/
- http://robotlolita.me/2011/10/09/understanding-javascript-oop.html
Trening czyni mistrza
Jeżeli chcesz sobie potrenować zdobytą wiedzę, zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania