Rest-Parameter

Der Rest-Parameter umfasst alle Argumente innerhalb einer Funktion, die NICHT als Parameter in der Funktionsdeklaration erfasst wurden (sogenannte überzählige Argumente).

Important

Übergabe von Argumenten an eine Funktion

Was passiert mit überzähligen Argumenten?

// Übergabe von Argumenten an Funktion
// was passiert mit überzähligen Argumenten

function jessyDatas (name, age, address) {
    console.log(name, age, address);
    console.log(arguments);   // arguments = Arguments Objekt -> enthält alle übergebenen Argumente
    for (let arg of arguments) {
        console.log(arg);
    }
    console.log(arguments.length);
    console.log(arguments[3]);
}

jessyDatas("Jessica", 36, "Dragonroad 66", 165, 65);

Seit ES6 verfügbar. Enthält nur die überzähligen Parameter.

/*
    Rest-Parameter seit ES6
    ...params - Syntax fasst zusammen
    nimmt überzählige Argumente auf (die nicht von Parametern aufgefangen wurden)
    liefert Array mit überzähligen Argumenten
*/

function jessyDatas2 (name, age, address, ...params) {
    console.log(name, age, address);
    console.log(params);   // params = Arguments Objekt -> enthält nur überzählige Argumente
    console.log(params.length);
    params.forEach( arg => console.log(arg));
}

jessyDatas2("Jessica", 36, "Dragonroad 66", 165, 65);

let add = (...params) => params.reduce( (a,b) => a + b, 0);  // '0' ist Initialwert
console.log(add(2,3));  // 5
console.log(add(1,2,3,4,5,6,7,8,9));  // 45

let greeting = (floskel, ...heroes) =>
    heroes.forEach(hero => console.log(`${floskel} ${hero}`));
greeting("Heyho", "Jessy", "Luke", "Thor", "Odin");     // Heyho Jessy -> Heyho Luke -> ...

let heroArr = ["Jessy", "Luke", "Thor", "Odin", "Elektra"];
greeting("Hi", ...heroArr);   // Hi Jessy -> Hi Luke -> ...

Events und Event-Object

Events durchlaufen drei Phasen:

  1. Capturing-Phase (Window –> Target / “from top down to target”)

  2. Target-Phase

  3. Bubbling-Phase (Target –> Document / “from bottom up to document”): Standardmäßig wird hier auf Event reagiert

[element].addEventListener(
    event,          Event-Type
    function,       aufzurufende Funktion
    useCapture      Phase der Aktivierung bei Elementen welche höher in DOM sind als Target
                    true: Event wird bereits in Capturing-Phase ausgelöst
                    false: Event wird erst in Bubbling-Phase ausgelöst
                    (Default: false)
)

Beispiel:

                 | |  / \
-----------------| |--| |-----------------
| element1       | |  | |                |
|   -------------| |--| |-----------     |
|   |element2    \ /  | |          |     |
|   --------------------------------     |
|        W3C event model                 |
------------------------------------------

Bei Klick auf element2 wird bei

element1.addEventListener('click',doSomething2,true)
element2.addEventListener('click',doSomething,false)

das Event auf element1 bereits in der Capturing-Phase, also vor element2 ausgeführt, sprich, doSomething2 erfolgt vor doSomething.

Bei

element1.addEventListener('click',doSomething2,false)
element2.addEventListener('click',doSomething,false)

hingegen durchläuft das Event element1 ohne das der Handler reagiert, es wird daraufhin zuerst doSomething ausgeführt, dann beginnt die Bubbling-Phase, erst dann wird bei Wieder-Hochlaufen doSomething2 ausgeführt.

let bodyTag = document.body;
bodyTag.addEventListener("click", () => console.log("Body hat in Bubbling-Phase reagiert"), false);
bodyTag.addEventListener("click", () => console.log("Body hat in Target-Phase reagiert"), true);

let link_1 = document.querySelector("nav li:first-of-type a");
let link_2 = document.querySelector("nav li:last-of-type a");
let item_1 = link_1.parentNode;
let item_2 = link_2.parentNode;

/*
    event.preventDefault()
    unterdrückt Standardverhalten von Links, Formularen etc.

    event.stopPropagation()
    unterbindet Weitergabe des Events - Eventfluss wird unterbrochen
*/

link_1.addEventListener("click", (event) => {
    event.preventDefault();
    console.log("Event hat Link 1 in Target-Phase erreicht");
});
item_1.addEventListener("click", () => console.log("Event hat Item 1 erreicht"));

link_2.addEventListener("click", (event) => {
    event.stopPropagation();
    console.log("Event hat Link 2 in Target-Phase erreicht");
});
item_2.addEventListener("click", () => console.log("Event hat Item 2 erreicht"), true);

/*
    generelle Eigenschaften des Event-Objektes
    [event].type            - Event-Typ
    [event].target          - Element, auf dem Event ausgeführt werden (alternativ zu 'this')
    [event].currentTarget   - Element, auf dem Handler registriert wurde
    [event].timeStamp       - Zeitpunkt des Ereignisses
    [event].eventPhase      - Ereignisphasen (1,2,3)

    [event].cancelable      - true / false
    Abfrage geeignet, um herauszufinden, ob ein Ereignis, das mit Standardaktion
    verbunden, dieses durch Aufruf von preventDefault() unterbrochen werden kann
*/

let liste = document.querySelector("#liste");

// "click" reagiert NUR auf die linke Maustaste
liste.addEventListener("mousedown", (event) => {

    // Event-Objekt
    console.log(event);     //  mousedown { target: li, buttons: 1, ... }

    // Event, das direkt vom Ereignis betroffen
    console.log(event.target);  // li-Element

    // Element, auf dem Event registriert wurde
    console.log(event.currentTarget);   // ul (Parent)

    // Event-Phase ermitteln
    console.log(event.eventPhase);  // 3 --> Bubbling-Phase

    let phaseObj = {
        1: "Capturing -Phase",
        2: "Target-Phase",
        3: "Bubbling-Phase"
    }
    console.log(phaseObj[event.eventPhase]);

    //  Maustaste, die gedrückt wurde
    console.log(event.button);

    let mouseObj = {
        0: "Linke Maustaste",
        1: "Mittlere Maustaste",
        2: "Rechte Maustaste"
    };
    console.log(mouseObj[event.button]);  // z.B. 0 --> Linke Maustaste

    // Kontextmenü unterdrücken (Bei Klick mit rechter Maustaste)
    liste.oncontextmenu = function(event) {
        event.preventDefault();
        // return false;    // hier auch möglich
    };
});

// Tastatur-Ereignisse
let field = document.querySelector("#field");

let keyPressFunc = function(event) {
    console.log(event.key);
    console.log(event.which);  // Tastatur-Code der Taste
    if (event.shiftKey) console.log(event.which, "mit Umschalttaste");
};
field.addEventListener("keypress", keyPressFunc);

let klicker = function(event) {
    console.log("Bildschirm-X-Position", event.screenX);
    console.log("Bildschirm-Y-Position", event.screenY);
    console.log("Browser-X-Position", event.clientX);
    console.log("Browser-Y-Position", event.clientY);
}
document.onclick = klicker;

// window-scroll-Ereignis
window.addEventListener("scroll", () => {
    console.log(scrollX, scrollY);
});

/*
    EventListener und once
    EventListener hat zusätzliche Option 'once' - diese entfernt Event-Listener
    automatisch nach 1. Auftreten des Events

            [elem].addEventListener(event, function,  {once: true});

    Aufräumen und Ausschalten des Listeners erfolgt automatische
    das hält Code übersichtlicher und Speicherverwaltung effizienter
    -> alle Variablen die zum callback gehören werden vom garbage collector gelöscht

    Event-Listener wird automatisch gelöscht, sofern dieser nicht mehr gebraucht wird.
*/

liste.addEventListener("mousedown", function(event) {
    console.log("einmaliges Ereignis", event.eventPhase);
}, {once: true, capture: true})

Destructuring

Werte aus Arrays und Objekten extrahieren und in passende Variablen / Konstanten überführen. Prinzip des Destructuring (destrukturierende Anweisung) ermöglicht es, Werte, die in Objekten oder Arrays hinterlegt sind, relative einfach an Variablen / Konstanten zuzuweisen.

Array-Destructuring

let heroes = ["Jessy", "Luke", "Odin", "Elektra"];

// ohne Destructuring
let hero_1 = heroes[0];
let hero_2 = heroes[1];
let hero_3 = heroes[2];
let hero_4 = heroes[3];

// Array auf Grundlage von Variablen erstellen
let heroArr = [hero_1, hero_2, hero_3, hero_4];

// mit Destructuring
let [one, two, three, four, five] = heroes;
console.log(one, two);
console.log(five);  // undefined, da überzählig

// Destructuring für Funktions-Parameter nutzen
function getNames ([one, two, three, four]) {
    console.log(three, four);
};
getNames(heroes);

// bei existierenden Variablen entfällt 'let'
// Arbeit mit default-Werten ist möglich
[one, two, three, four = "a hero", five = "no hero"] = heroArr;
console.log(four);  // Elektra
console.log(five);  // no hero

// nur bestimmte Werte extrahieren
let [myHero1,,,myHero4] = heroes;
console.log(myHero1, myHero4);

// Werte aus mehrdimensionalen Arrays überführen
let coords = [
    [1,2,3],
    [4,5,6],
    [7,8,9]
];

let [
    [a,b,c],
    [d,e,f],
    [g,h,i]
] = coords;

console.log(a,e,i);  // 1 5 9

Objekt-Destructuring

let favHero = {
    firstName: "Jessica",
    lastName: "Jones"
};

let {
    firstName:  firstFromObj,
    lastName:   lastFromObj
} = favHero;
console.log(firstFromObj, lastFromObj);     // Jessica Jones

// einfacher, wenn key-Bezeichner = Variablen-Bezeichner
let {
    firstName,
    lastName
} = favHero;
console.log(firstName, lastName);      // Jessica Jones

// Werte aus geschachtelten Objekten
let jessyData = {
    firstName:  "Jessica",
    age:        36,
    address: {
        postCode:   111,
        street:     "Dragonroad 66",
        city:       "Hells Kitchen"
    }
}
let {
    firstName: heroFirst,
    age,
    address: {
        postCode:   zip,
        street,
        number,
        city
    }
} = jessyData;
console.log(heroFirst, age, zip, street, city);     // Jessica 36 111 Dragonroad 66 Hells Kitchen
console.log(number);    // undefined, da keine Entsprechung im Objekt

// 'let' entfällt, wenn Variable bereits vorhanden
({firstName} = jessyData);  // runde Klammern notwendig, damit Destructuring erkannt wird
console.log(firstName);

Kombination Array- und Object-Destructuring

let jessyData = {
    firstName:  "Jessica",
    age:        36,
    address: {
        postCode:   111,
        street:     "Dragonroad 66",
        city:       "Hells Kitchen"
    },
    phone: [
        "0172-567894321",
        "999-66339955"
    ]
};

let {
    firstName,
    address: { city },
    phone:  [mobile, office]
} = jessyData;

console.log(firstName, city, mobile, office);  // Jessica Hells Kitchen 0172-567894321 999-66339955

let marvelHeroes = [
    {
        name:       "Odin",
        contact:    {
            mail:   "odin@valhalla.org",
            phone:  "534698721"
        }
    },
    {
        name:       "Thor",
        contact:    {
            mail:   "thor@valhalla.org",
            phone:  "534698721"
        }
    },
    {
        name:       "Luki",
        contact:    {
            mail:   "loki@valhalla.org",
            phone:  "534698721"
        }
    },
];

// Destructuring in for-Schleife nutzen
for (let { name, contact: { mail, phone } } of marvelHeroes) {
    console.log(name, mail, phone);   //  Odin odin@valhalla.org 534698721 -> ...
}

// Destructuring für Parameter einer Funktion nutzen
let chefchen = {
    name:       "Odin",
    contact:    {
        mail:   "odin@valhalla.org",
        phone:  "534698721"
    }
};

function heroMail( { contact: { mail } } ) {
    console.log(mail);  // odin@valhalla.org
}
heroMail(chefchen);

Factory-Function

Über eine Factory-Funktion kann ein Objekt als Rückgabewert einer Funktion definiert werden. Dazu muss das Objekt in runde Klammern () geschrieben werden, damit die Objektklammern ({}) nicht als Funktions-Klammern fehlinterpretiert werden bzw. der Interpreter diese eindeutig als Objekt-Klammern erfassen kann:

Hint

Die Factory-Funktion definiert, wie ein Objekt aussehen soll. Sie gibt das Objekt beim Aufruf mit den definierten Argumenten aufgerufen wird, zurück.

// runde Klammern nötig, damit {} als Objekt-Klammern und nicht als Funktions-Klammern
// interpretiert werden
// hier: implizit wird erstelltes Objekt zurückgegeben
const createUserObj = (user, avatar) => ({
    user,
    avatar,
    setName(newName) {
        this.user = newName;
        return this;
    }
});

let user1 = createUserObj("Jessy", "jessy_avatar.png");
let user2 = createUserObj("Luke", "luke_face.jpg");

console.log(user1);
console.log(user2);
console.log(user1.setName("Pinky"));    // überschreibt Namen

Constructor-Function

Sollten IMMER mit Großbuchstaben beginnen. Muss mit new aufgerufen werden: erstellt ein neues Objekt und gibt es zurück.

function Item (prodName, prodPrice, prodCat, prodQty) {
    this.name = prodName;
    this.price = prodPrice;
    this.cat = prodCat;
    this.qty = prodQty;
    this.shortPrint = function() {
        return  `${this.name} - ${this.price} EUR`;
    };
};
// Instanziieren
let prod_1 = new Item("Talisker", 49.99, "Whiskey", 500);
let prod_2 = new Item("Chivas Regal", 29.99, "Office Drinks", 1000);

console.log(prod_1);
console.log(prod_2);
console.log(prod_1.shortPrint());

let prod_3 = new Item("Talisker Limited Edition", 359.99, "HomeDrinks", 250);
prod_3.slogan = function(msg) {
    return `${this.name} ${msg}`;
};

console.log(prod_3.slogan("in der limitierten Sonderauflage"));
// console.log(prod_2.slogan("in der limitierten Sonderauflage"));   // geht nicht!

Über .prototype lassen sich Attribute, also Funktionen oder Variablen an Objekte hinzufügen, welche auf alle, auch bereits instantiierten, Instanzen eines Objekt angewendet werden:

// Nachträgliche Funktion für alle Objekt-Instanzen definieren (auch wenn Objekte schon
// instantiiert)
Item.prototype.slogan = function(msg) {
    return `${this.name} ${msg}`;
}
console.log(prod_2.slogan("in der limitierten Sonderauflage"));   // das geht!

// prototype war lange Zeit wichtig, um Browser neue Methoden beizubringen
// mittlerweile nicht mehr so bedeutend, da alle Browser auf ähnlichem Stand sind

Über instanceof kann geprüft werden ob ein Objekt sich von einem anderen Objekt ableitet, d.h. ob sich die Prototypenkette des einen Objekts in dem zweiten wiederfindet.

// instanceof-Operator  (in gleicher Prototypenkette wie ...)
// prüfen ob ein Objekt von einem anderen ableitet - diesen als Prototypen haben
console.log(prod_2 instanceof Item);    // true
console.log(prod_2 instanceof Object);  // true
console.log(prod_2 instanceof Array);   // false