Object.create()
Seit ES6 ist Object.create() immer seltener im Einsatz (kommt von ES5):
// Object-Erstellung auf Grundlage eines Prototypen
// Prototyp
let whiskey = {
name: "",
cat: ["Whiskey"],
price: 0,
quantity: 500,
shortPrint () {
console.log(`${this.cat[0]}: ${this.name} - ${this.price}`);
},
slogan(msg) {
console.log(`${this.name} - ${msg}`);
}
};
console.log(whiskey);
whiskey.shortPrint();
// Objekt auf Grundlage des Prototypen erstellen
let newProd = Object.create(whiskey); // alle vererbten Eigenschaften unter 'prototype'
console.log(newProd);
console.log(newProd.price);
// eigene Daten definieren
// wird direkt im Objekt hinzugefügt
newProd.name = "Shivas Regal";
newProd.price = 29.99;
// Referenz-Objekt, daher wird Wert unter 'prototype' verändert
newProd.cat.unshift("Office Drinks");
console.log(newProd);
console.log(newProd.price);
newProd.shortPrint();
console.log(whiskey); // Referenz-Objekt (cat) wurde AUCH HIER verändert, da Referenz-Objekt
Eigenschaftsattribute
Für Eigenschaften eines Objekts lassen sich Attribute definieren. Falls diese nicht gesetzt werden, gilt der Default.
/*
zur Konfiguration von Object-Properties
Attribut Nutzung
value Wert einer Eigenschaft
writable gibt an, ob Eigenschaft überschreibbar ist
(nachträgliche Änderung des values)
default: false
enumerable gibt an, ob Eigenschaft iterierbar ist
(ob sie in Iteration geliefert wird)
default: false
configurable gibt an, ob Eigenschaft konfigurierbar ist
(ob sie bspw. gelöscht werden kann)
default: false
set definiert Funktion, mit der schreibend auf Eigenschaft
zugegriffen werden kann (nur für Zugriffseigenschaften)
get definiert Funktion, mit der lesend auf Eigenschaft
zugegriffen werden kann (nur für Zugriffseigenschaften)
*/
// Prototyp, der eine Methode beinhaltet
let heroActions = {
getHeroDatas () {
return `${this.name} (${this.age} wohnt in ${this.address})`;
}
};
// Objekt auf Grundlage des Prototypen erstellen
let hero = Object.create(heroActions, {
name: {
// initialer Wert der Eigenschaft (propertiesObject)
value: "Jessica",
writable: true, // Eigenschaft ist überschreibbar
enumerable: true, // Eigenschaft ist iterierbar
configurable: true // Eigenschaft ist konfigurierbar
},
age: {
value: 36
},
address: {
value: "Dragonroad",
writable: true
}
});
hero.name = "Elektra"; // OK
// hero.age = 29; // TypeError: "age" is read-only
// delete hero.age; // TypeError: property "age" is non-configurable and can't be deleted
// delete hero.address; // TypeError: property "address" is non-configurable and can't be deleted
hero.address = "Alexanderplatz 111, Berlin"; // OK
console.log(hero); // object
for (let key in hero) {
console.log(key); // nur 'name' und 'getHeroDatas' (Rest nicht iterierbar)
// prototype-Einträge werden standardmäßig ebenfalls berücksichtigt
// nur EIGENE Eigenschaften berücksichtigen
if (hero.hasOwnProperty(key)) {
console.log(key); // nur 'name'
}
}
Neue Properties in Prototypen definieren
Object.prototype wird um weitere Attribute (hier: Funktionen) erweitert.
Important
Jedes Objekt in JavaScript besitzt eine eingebaute Eigenschaft, die prototype genannt wird.
Dieser prototype ist selbst wieder ein Objekt, und hat daher ebenfalls einen eigenen prototype.
So entsteht eine sogenannte Prototype-Kette (prototype chain).
Die Kette endet, sobald ein prototype erreicht wird, dessen eigener prototype den Wert null hat.
Prototypen erlauben die Vererbung von Properties in JavaScript. Wenn ein Property eines Objekts aufgerufen wird, wird entlang der Prototype-Kette nach diesem Property gesucht und das erste gefundene Match verwendet.
Object.prototype.print = function() {
console.log("print-function", this);
}
// hero Objekt von oben
hero.print();
NodeList.prototype.giveBack = function() {
console.log("giveBack hello");
};
document.querySelectorAll("li").giveBack(); // OK, da NodeList diese Methode hat
// document.querySelector("li").giveBack(); // NOK, da Node diese Methode nicht hat
// Event mit on() registrieren
Node.prototype.on = function (event, fn) {
this.addEventListener(event, fn);
return this;
};
// ... und mit off() de-registrieren
Node.prototype.off = function (event, fn) {
this.removeEventListener(event, fn);
return this;
};
let giveAlert = function() {
alert("Es hat Klick gemacht");
};
document.querySelector("li").on("click", giveAlert); // Event-Listener registrieren
document.querySelector("li").off("click", giveAlert); // Event-Listener de-registrieren
Prototype einem Objekt zuordnen
Mittlerweile über die Object.setPrototypeOf() um einen Prototypen für ein
Objekt zuzuweisen.
/*
setzen / zuordnen eines Prototypen
__proto__ (deprecated) || Object.setPrototypeOf()
*/
// Prototyp, der Methode beinhaltet
let heroActions = {
getHeroDatas () {
return `${this.name} (${this.age} wohnt in ${this.address})`;
}
};
let heroDatas = {
name: "Jessica Jones",
age: 36,
address: "Dragonraod 66"
};
// Object.setPrototypeOf(object, prototype);
// Füge 'heroActions' unter Prototypenn von 'heroDatas' hinzu
Object.setPrototypeOf(heroDatas, heroActions);
console.log(heroDatas.getHeroDatas()); // Jessica Jones (36 wohnt in Dragonraod 66)
Klassensyntax
JS macht kein “richtiges” OOP, es fühlt sich aber so an.
class Product {
// constructor = Herzstück der Klasse
// wird beim Instanziieren zuerst ausgeführt
constructor(prodName, prodPrice, prodCat, prodQty) {
this.name = prodName;
this._price = prodPrice; // '_' um Property nicht mit Setter zu verwechseln
this.cat = prodCat;
this.qty = prodQty;
}
// über 'get' wird Funktion als Getter gekennzeichnet und ist nun eine Eigenschaft
get shortPrint() {
return `${this.name}: ${this._price} EUR (${this.qty} Stück auf Lager)`;
}
// hier kann arrow-function verwendet werden, da Objekt bereits erzeugt ist
// Funktion wird im Objekt selbst verankert
logThis = () => console.log(this);
// über 'set' wird Funktion als Setter gekennzeichnet und ist nun eine Eigenschaft
// Eigenschaft/Funktion wird im 'prototype' verankert
set addQty(qty) {
this.qty += qty;
}
set subQty(qty) {
(this.checkQty(qty)) ? this.qty -= qty : console.log("unzureichender Lagerbestand");
}
checkQty(qty) {
return (this.qty - qty > 1);
}
get price() {
return this._price;
}
set price(newVal) {
this._price = newVal;
}
};
let newProd = new Product("Talisker", 49.99, "Whiskey", 500);
console.log(newProd);
newProd.logThis();
// geht nicht mehr wegen 'set' Bezeichner ...
// newProd.subQty(250);
// newProd.addQty(100);
// newProd.subQty(500);
// ... muss nun als Eigenschaft verändert werden
newProd.subQty = 250; // 500 - 250 -> 250
newProd.addQty = 100; // 250 + 100 -> 150
newProd.subQty = 500; // 350 - 500 -> -150: checkQty verhindert Abzug von 500 Stück
console.log(newProd);
console.log(newProd.shortPrint); // Talisker: 49.99 EUR (350 Stück auf Lager)
/*
Bezeichner für Getter und Setter müssen sich von Eigenschaften unterscheiden,
damit es nicht zu Endlos-Rekursionen, Stack-Überlauf und letztlich
Programmabsturz kommt
oft werden Eigenschaften, auf die nicht direkt zugegriffen werden soll,
mit '_' geprefixt - dann sind Zugriffsmethoden vorhanden!
Jede Klasse in eigenem Skript verorten!
*/
Eigenschaftsattribute für Properties setzen
// Vorhandene Property
// -------------------
// im Objekt sind alle schon vorhandenen Attribute standardmäßig überschreibbar,
// sofern die Eigenschaftattribute nicht anders gesetzt wurden
Object.defineProperty(newProd, "name", {
writable: false
}
);
// bei Erzeugung von Objekten werden Eigenschaftsattribute auf 'true' gesetzt,
// wenn sie ohne Konfiguration der Eigenschaftsattribute erzeugt wurden
// newProd.name = "whatever"; // TypeError: "name" is read-only
// Neue Property
// -------------
Object.defineProperty(newProd, "desc", {
value: "Best Quality Whiskey",
enumerable: true
// defineProperty: writable standardmäßig auf false -> read-only
});
// newProd.desc = "BliBlaBlub"; // TypeError: "desc" is read-only
for (let key in newProd) {
console.log(key);
}
Arbeit mit Settern und Gettern der Eigenschaftsattribute
Über Object.defineProperty() wird eine neue Property an ein bereits vorhandene
Instanz eines Objekts hinzugefügt:
let qualityVal = "Fine Best Strong...";
Object.defineProperty(newProd, "quality", {
get() {
return qualityVal;
},
set(newVal) {
qualityVal = newVal;
},
enumerable: true
});
console.log(newProd);
newProd.quality = "Finest";
console.log(newProd.quality); // Finest
Kind-Klasse erstellen (Klassen erweitern)
class LtdProduct extends Product {
constructor (name, cat, price, qty, limit) {
// immer zuerst super() aufrufen! (sonst Fehler)
// danach neue / spezielleren Eigenschaften hinzufügen
super(name, price, cat, qty);
this.limit = limit;
}
// Methoden der Elternklasse stehen zur Verfügung
// könnt diese nutzen, ändern oder überschreiben
set addQty(qty) {
(this.checkLimit(qty)) ? super.addQty = qty : console.log("Limit überschritten");
}
checkLimit(qty) {
return !(this.qty + qty > this.limit);
}
}
let ltdProduct = new LtdProduct("Talisker Limited Edition",
"Home Drinks", 399.50, 250, 500);
console.log(ltdProduct);
ltdProduct.subQty = 59;
console.log(ltdProduct.shortPrint);
ltdProduct.addQty = 50;
Statische Objekt-Eigenschaften (Methoden & Properties)
Statische Eigenschaften und Methoden können nur auf der Klasse angewandt werden, nicht auf der Instanz dieser Klasse.
// Statische Methoden
// -> Anwendung nur auf Klasse, nicht auf Instanzen
class Product {
constructor(prodName, prodPrice, prodCat, prodQty, prodArt) {
this.name = prodName;
this._price = prodPrice;
this.cat = prodCat;
this.qty = prodQty;
this.art = prodArt;
}
get shortPrint() {
return `${this.name}: ${this._price} EUR (${this.qty} Stück auf Lager)`;
}
logThis = () => console.log(this);
set addQty(qty) {
this.qty += qty;
}
set subQty(qty) {
(this.checkQty(qty)) ? this.qty -= qty : console.log("unzureichender Lagerbestand");
}
checkQty(qty) {
return (this.qty - qty > 1);
}
get price() {
return this._price;
}
set price(newVal) {
this._price = newVal;
}
// Statische Methode
static getWhiskey() {
return {
SL: "Scotch",
BB: "Bourbon",
IRE: "Irish"
};
};
// Statische Eigenschaft innerhalb der Klasse definieren
// Achtung: stets im SCREAMING_SNAKE_CASE schreiben
static artObj = {
SL: "Scotch",
BB: "Bourbon",
IRE: "Irish"
}
};
// Statische Eigenschaft außerhalb eines Objekts definieren -> VERALTET!!
Product.WHISKEY_ART = {
SL: "Scotch",
BB: "Bourbon",
IRE: "Irish"
}
let newProd = new Product("Glennfiddich", 39.99, "Whiskey", 500, Product.getWhiskey().IRE);
let newProd2 = new Product("Glennfiddich", 39.99, "Whiskey", 500, Product.artObj.IRE);
let newProd3 = new Product("Glennfiddich", 39.99, "Whiskey", 500, Product.WHISKEY_ART.IRE);
console.log(newProd);
console.log(newProd2);
console.log(newProd3);
/*
statische Methoden und Eigenschaften:
- werden direkt auf Klasse aufgerufen
- werden nicht vererbt => sind also auf Instanzebene nicht verfügbar
*/
console.log(newProd.artObj); // undefined
console.log(newProd.WHISKEY_ART) // undefined
Symbole in Klassen
Über Object.getOwnPropertySymbols() lassen sich alle Symbole eines Objekts
zurückgeben:
const DOG_NAME = Symbol("dogname");
class Dog {
constructor (name) {
this[DOG_NAME] = name; // muss über Property-Access definiert werden
}
get name () {
return this[DOG_NAME];
}
set name(newVal) {
this[DOG_NAME] = newVal;
}
}
let myDog = new Dog("Snoopy");
myDog.name = "Leika";
console.log(myDog.name); // Leika
console.log(Object.getOwnPropertySymbols(myDog)); // [ Symbol("dogname") ]
myDog[Object.getOwnPropertySymbols(myDog)[0]] = "Moja";
console.log(myDog.name); // Moja
Seit ES10 ist es möglich mit # eine private Eigenschaft zu definieren:
// private Eigenschaften (beginnt mit '#')
// erst seit ES10 möglich
// -> Zugriff nur innerhalb der Klasse, nicht von außerhalb
class HomeAnimals {
#anzahl;
#initialCount;
constructor(count = 0) {
this.#initialCount = count;
this.reset(count); // --> #count = 0
}
reset(value = this.#initialCount) {
this.#anzahl = value;
}
addAnimal (count = 1) {
this.#anzahl += count;
}
subAnimal (count = 1) {
this.#anzahl -= count;
}
get actCount() {
return this.#anzahl;
}
}
let myAnimals = new HomeAnimals(); // myAnimals.#count = 0
console.log(myAnimals);
myAnimals.addAnimal(3); // myAnimals.#count = 3
myAnimals.addAnimal(); // myAnimals.#count = 4
myAnimals.reset(2); // myAnimals.#count = 2
myAnimals.subAnimal(); // myAnimals.#count = 1
console.log(myAnimals.actCount); // 1
console.log(myAnimals["#anzahl"]); // undefined --> da private Eigenschaft
console.log(HomeAnimals["#anzahl"]); // undefined --> da private Eigenschaft