JavaScript Fortgeschritten

Objekte in JavaScript meistern

Objekte in JavaScript meistern

Objekte sind das Herzstück von JavaScript. Fast alles in JS ist ein Objekt - Arrays, Funktionen, sogar Strings haben Objekt-Methoden. Zeit, Objekte wirklich zu verstehen!

Objekte erstellen

Object Literal (Standard)

const person = {
    name: "Max",
    alter: 25,
    stadt: "Berlin"
};

Mit Variablen (Property Shorthand)

const name = "Lisa";
const alter = 30;
const stadt = "Hamburg";

// Alt
const person1 = { name: name, alter: alter, stadt: stadt };

// Modern - Property Shorthand
const person2 = { name, alter, stadt };

console.log(person2);  // { name: "Lisa", alter: 30, stadt: "Hamburg" }

Computed Property Names

const eigenschaft = "dynamisch";

const obj = {
    statisch: "fester Name",
    [eigenschaft]: "dynamischer Name",
    [`prop_${1 + 1}`]: "prop_2"
};

console.log(obj.dynamisch);  // "dynamischer Name"
console.log(obj.prop_2);     // "prop_2"

Object.create()

const prototyp = {
    gruss() {
        return `Hallo, ich bin ${this.name}`;
    }
};

const person = Object.create(prototyp);
person.name = "Max";

console.log(person.gruss());  // "Hallo, ich bin Max"

Eigenschaften zugreifen

Dot Notation vs. Bracket Notation

const person = {
    name: "Max",
    "voller-name": "Max Mustermann",
    42: "Antwort"
};

// Dot Notation - Standard
console.log(person.name);  // "Max"

// Bracket Notation - für spezielle Fälle
console.log(person["name"]);        // "Max"
console.log(person["voller-name"]); // "Max Mustermann" (Bindestrich!)
console.log(person[42]);            // "Antwort" (Zahl als Key!)

// Dynamischer Zugriff
const key = "name";
console.log(person[key]);  // "Max"

Optional Chaining (?.)

const user = {
    name: "Max",
    adresse: {
        stadt: "Berlin"
    }
};

// Ohne Optional Chaining - kann crashen!
// console.log(user.kontakt.email);  // TypeError!

// Mit Optional Chaining - sicher
console.log(user.kontakt?.email);     // undefined
console.log(user.adresse?.stadt);     // "Berlin"
console.log(user.adresse?.plz);       // undefined

// Verkettung
console.log(user?.adresse?.land?.code);  // undefined

// Mit Methodenaufruf
const obj = {
    methode() { return "Hallo"; }
};
console.log(obj.methode?.());           // "Hallo"
console.log(obj.nichtExistent?.());     // undefined

Nullish Coalescing (??)

const config = {
    timeout: 0,
    retries: null
};

// || behandelt 0 und "" als falsy
console.log(config.timeout || 3000);   // 3000 (falsch!)
console.log(config.retries || 3);      // 3

// ?? nur bei null/undefined
console.log(config.timeout ?? 3000);   // 0 (korrekt!)
console.log(config.retries ?? 3);      // 3
console.log(config.missing ?? "default");  // "default"

Eigenschaften hinzufügen & ändern

const person = { name: "Max" };

// Hinzufügen
person.alter = 25;
person["stadt"] = "Berlin";

// Ändern
person.name = "Maximilian";

// Mit Object.assign
Object.assign(person, { hobby: "Coding", land: "Deutschland" });

// Mit Spread (neues Objekt!)
const erweitert = { ...person, beruf: "Entwickler" };

console.log(person);
// { name: "Maximilian", alter: 25, stadt: "Berlin", hobby: "Coding", land: "Deutschland" }

Eigenschaften entfernen

const person = {
    name: "Max",
    alter: 25,
    stadt: "Berlin"
};

// delete - verändert Original
delete person.alter;
console.log(person);  // { name: "Max", stadt: "Berlin" }

// Destructuring - neues Objekt ohne Eigenschaft
const { stadt, ...rest } = person;
console.log(rest);   // { name: "Max" }
console.log(person); // { name: "Max", stadt: "Berlin" } - unverändert!

Objekte prüfen

Eigenschaft existiert?

const person = {
    name: "Max",
    alter: undefined
};

// in - prüft auch Prototyp-Kette
console.log("name" in person);      // true
console.log("alter" in person);     // true (auch wenn undefined!)
console.log("toString" in person);  // true (geerbt!)

// hasOwnProperty - nur eigene Eigenschaften
console.log(person.hasOwnProperty("name"));      // true
console.log(person.hasOwnProperty("toString"));  // false

// Object.hasOwn (modern)
console.log(Object.hasOwn(person, "name"));  // true

Objekt-Typ prüfen

const arr = [1, 2, 3];
const obj = { a: 1 };
const fn = function() {};

console.log(typeof arr);  // "object" (nicht hilfreich!)
console.log(typeof obj);  // "object"
console.log(typeof fn);   // "function"

console.log(Array.isArray(arr));  // true
console.log(Array.isArray(obj));  // false

// Konstruktor prüfen
console.log(arr.constructor === Array);   // true
console.log(obj.constructor === Object);  // true

Objekte iterieren

Object.keys / values / entries

const person = {
    name: "Max",
    alter: 25,
    stadt: "Berlin"
};

// Schlüssel
const keys = Object.keys(person);
console.log(keys);  // ["name", "alter", "stadt"]

// Werte
const values = Object.values(person);
console.log(values);  // ["Max", 25, "Berlin"]

// Key-Value-Paare
const entries = Object.entries(person);
console.log(entries);
// [["name", "Max"], ["alter", 25], ["stadt", "Berlin"]]

Iterieren

const person = { name: "Max", alter: 25, stadt: "Berlin" };

// for...in (alle enumerable, inkl. geerbt)
for (const key in person) {
    console.log(`${key}: ${person[key]}`);
}

// Object.keys + forEach
Object.keys(person).forEach(key => {
    console.log(`${key}: ${person[key]}`);
});

// Object.entries + for...of (empfohlen)
for (const [key, value] of Object.entries(person)) {
    console.log(`${key}: ${value}`);
}

Objekte transformieren

Object.fromEntries

const entries = [
    ["name", "Max"],
    ["alter", 25]
];

const obj = Object.fromEntries(entries);
console.log(obj);  // { name: "Max", alter: 25 }

// Praktisch: Objekt transformieren
const preise = { apfel: 1.50, banane: 0.80, orange: 1.20 };

const erhoehtePreise = Object.fromEntries(
    Object.entries(preise).map(([key, value]) => [key, value * 1.1])
);
console.log(erhoehtePreise);
// { apfel: 1.65, banane: 0.88, orange: 1.32 }

Objekte filtern

const scores = { Max: 85, Lisa: 92, Tom: 45, Anna: 78 };

// Nur bestandene (>= 60)
const bestanden = Object.fromEntries(
    Object.entries(scores).filter(([_, score]) => score >= 60)
);
console.log(bestanden);  // { Max: 85, Lisa: 92, Anna: 78 }

Objekte kopieren

Shallow Copy (flach)

const original = {
    name: "Max",
    adresse: { stadt: "Berlin" }
};

// Spread
const kopie1 = { ...original };

// Object.assign
const kopie2 = Object.assign({}, original);

// Problem: Nested Objects sind Referenzen!
kopie1.name = "Lisa";
kopie1.adresse.stadt = "Hamburg";

console.log(original.name);          // "Max" (OK)
console.log(original.adresse.stadt); // "Hamburg" (geändert!)

Deep Copy (tief)

const original = {
    name: "Max",
    adresse: { stadt: "Berlin" }
};

// Mit JSON (einfach, aber limitiert)
const deepCopy1 = JSON.parse(JSON.stringify(original));

// Mit structuredClone (modern)
const deepCopy2 = structuredClone(original);

deepCopy2.adresse.stadt = "Hamburg";
console.log(original.adresse.stadt);  // "Berlin" (unverändert!)

Objekte zusammenführen

Object.assign

const defaults = { theme: "light", lang: "de", debug: false };
const userPrefs = { theme: "dark", notifications: true };

const config = Object.assign({}, defaults, userPrefs);
console.log(config);
// { theme: "dark", lang: "de", debug: false, notifications: true }

Spread Operator

const defaults = { theme: "light", lang: "de" };
const userPrefs = { theme: "dark" };

const config = { ...defaults, ...userPrefs };
console.log(config);  // { theme: "dark", lang: "de" }

// Tiefe Zusammenführung
const obj1 = { a: { b: 1 } };
const obj2 = { a: { c: 2 } };

// Spread ist flach!
const merged = { ...obj1, ...obj2 };
console.log(merged);  // { a: { c: 2 } } - b ist weg!

// Tiefe Zusammenführung manuell
const deepMerged = {
    a: { ...obj1.a, ...obj2.a }
};
console.log(deepMerged);  // { a: { b: 1, c: 2 } }

Objekte einfrieren

Object.freeze

const config = {
    apiUrl: "https://api.example.com",
    timeout: 5000
};

Object.freeze(config);

config.timeout = 10000;  // Ignoriert (strict mode: Error)
config.newProp = "test"; // Ignoriert
delete config.apiUrl;    // Ignoriert

console.log(config);  // Unverändert!

// Achtung: Nur flach!
const obj = { nested: { value: 1 } };
Object.freeze(obj);
obj.nested.value = 99;  // Funktioniert!

Object.seal

const obj = { a: 1, b: 2 };

Object.seal(obj);

obj.a = 10;      // OK - Ändern erlaubt
obj.c = 3;       // Ignoriert - Hinzufügen verboten
delete obj.b;    // Ignoriert - Löschen verboten

Prüfen

const frozen = Object.freeze({ a: 1 });
const sealed = Object.seal({ a: 1 });
const normal = { a: 1 };

console.log(Object.isFrozen(frozen));  // true
console.log(Object.isSealed(sealed));  // true
console.log(Object.isExtensible(normal));  // true

Objekt-Methoden

Method Shorthand

const person = {
    name: "Max",

    // Alt
    gruss: function() {
        return `Hallo, ich bin ${this.name}`;
    },

    // Modern - Method Shorthand
    verabschieden() {
        return `Tschüss von ${this.name}`;
    }
};

Getter & Setter

const person = {
    vorname: "Max",
    nachname: "Mustermann",

    // Getter
    get vollername() {
        return `${this.vorname} ${this.nachname}`;
    },

    // Setter
    set vollername(name) {
        const [vorname, nachname] = name.split(" ");
        this.vorname = vorname;
        this.nachname = nachname;
    }
};

console.log(person.vollername);  // "Max Mustermann"
person.vollername = "Lisa Schmidt";
console.log(person.vorname);     // "Lisa"
console.log(person.nachname);    // "Schmidt"

Praktisches Beispiel

const konto = {
    _guthaben: 1000,  // Konvention: _ = "privat"

    get guthaben() {
        return `${this._guthaben.toFixed(2)} €`;
    },

    set guthaben(betrag) {
        if (betrag < 0) {
            throw new Error("Guthaben kann nicht negativ sein");
        }
        this._guthaben = betrag;
    },

    einzahlen(betrag) {
        this._guthaben += betrag;
        return this;  // Für Chaining
    },

    abheben(betrag) {
        if (betrag > this._guthaben) {
            throw new Error("Nicht genug Guthaben");
        }
        this._guthaben -= betrag;
        return this;
    }
};

konto.einzahlen(500).abheben(200);
console.log(konto.guthaben);  // "1300.00 €"

Nützliche Patterns

Default-Werte

function createUser(options = {}) {
    const defaults = {
        name: "Gast",
        role: "user",
        active: true
    };

    return { ...defaults, ...options };
}

const user1 = createUser();
// { name: "Gast", role: "user", active: true }

const user2 = createUser({ name: "Max", role: "admin" });
// { name: "Max", role: "admin", active: true }

Pick - Nur bestimmte Keys

const person = { name: "Max", alter: 25, stadt: "Berlin", email: "max@test.de" };

function pick(obj, keys) {
    return Object.fromEntries(
        keys.filter(key => key in obj).map(key => [key, obj[key]])
    );
}

const nameUndEmail = pick(person, ["name", "email"]);
console.log(nameUndEmail);  // { name: "Max", email: "max@test.de" }

Omit - Bestimmte Keys weglassen

function omit(obj, keys) {
    return Object.fromEntries(
        Object.entries(obj).filter(([key]) => !keys.includes(key))
    );
}

const ohneEmail = omit(person, ["email"]);
console.log(ohneEmail);  // { name: "Max", alter: 25, stadt: "Berlin" }

Zusammenfassung

OperationSyntax
Erstellen{ } oder Object.create()
Zugriffobj.key oder obj["key"]
Sicherer Zugriffobj?.nested?.value
Defaultvalue ?? default
KeysObject.keys(obj)
ValuesObject.values(obj)
EntriesObject.entries(obj)
Kopieren{ ...obj } oder structuredClone()
Zusammenführen{ ...obj1, ...obj2 }
EinfrierenObject.freeze(obj)

Übung: Erstelle ein createStore Funktion, die einen einfachen State-Store mit get(), set(key, value) und subscribe(callback) Methoden zurückgibt!