Javascript: Eine Skriptsprache mit dynamischer Typisierung

Sowohl “Skriptsprache” als auch “dynamische Typisierung” sind sehr schwammige Begriffe
Unterschiedliche Ausprägungen je nach konkret vorliegender Sprache
Skriptsprache
  • Abhängigkeit von einer Laufzeitumgebung (Runtime, Virtual Machine)
    • Ursprüngliche Umgebung: Browser
    • Mittlerweile auch typisch: Server
  • Keine Vorab-Kompilierung in Maschinencode (aber möglicherweise JIT-Kompilierung)
Dynamische Typisierung
  • Typüberprüfungen werden erst zur Laufzeit vorgenommen
  • Typen können zur Laufzeit erweitert werden

ECMA-Script?

Standard 262 bei der European Computer Manufacturers Association
“JavaScript” ist streng genommen nur der Name der Implementierung von Netscape / Mozilla
“JavaScript” ist ein werbewirksamer Name
Syntax ein bisschen ähnlich zu Java, ansonsten kaum Gemeinsamkeiten

Zurückhaltende Buchempfehlung

“JavaScript: The Good Parts”, von Douglas Crockford
Mittlerweile leider etwas veraltet, aber ein guter Einstieg für Leute die Erfahrung mit anderen Programmiersprachen haben

JavaScript-Umgebungen

Wichtige Unterscheidung: Die Programmiersprache JavaScript kann in den unterschiedlichsten Umgebungen laufen
  • Auf einem Server (Zugriff auf Dateisystem, Datenbanken, …)
  • Im Browser (Zugriff auf DOM-Baum, Ortungsdienste, Webcam, …)
  • Als Teil einer Smartphone-App (Wie Browser, zusätzlich: Benutzerkennung, Dateisystem, …)
  • Als Teil einer beliebigen Desktop-Anwendung
In allen Fällen: Identische Sprache, andere verfügbare Objekte und Funktionen
Ein Server hat keine Webcam, ein Browser keinen Dateisystemzugriff, …
In den meisten Fällen: Kommunikation zwischen Umgebungen erfolgt über spezielle Protokolle
  • Nicht möglich: “Mal eben so” eine Funktion auf dem Server aufrufen (“Da ist doch auch JavaScript”)
  • Für Browser und Server typischerweise AJAX (wird später behandelt)
In dieser Veranstaltung: Die Programmiersprache JavaScript. Die spezifischen Laufzeitumgebungen für Server und Client sind Teil separater Vorlesungsteile.

Grundlegende Syntax

Geschweifte Klammern ({ }) für Blöcke (z.B. bei Schleifen oder Verzweigungen)
  • Wie in C, C++, C#, Java …
  • Anders als in Pascal & Visual Basic
Ausdrücke mit Infix-Notation ((1 + 2) / 5)
Wie in C, C++, C#, Java, Pascal, Visual Basic …
Semikolon (;) zur Abgrenzung von Anweisungen
Wie in C, C++, C#, Java, Pascal …
Funktionsaufrufe mit runden Klammern (roundNumber(1.5123, 3))
Wie in C, C++, C#, Java, Pascal, Visual Basic …
Kein explizites Hauptprogramm
Code wird sofort ausgeführt, wenn er evaluiert wird

Datentypen und Notation von Konstanten

Grundsätzlich wie JSON
Siehe Vorlesungsunterlagen
Allerdings ist Javascript etwas weniger strikt:
  • Schlüssel für Objekte müssen nicht zwingend in Anführungszeichen gesetzt werden
  • Abschließende Kommata in Listen und Objekten sind erlaubt
  • Als Anführungszeichen für Strings sind auch Paare von einfachen Hochkommata (') erlaubt

Ausgabe von Daten

Aufgrund der unterschiedlichen Umgebungen: Kein typischer Kanal wie stdout verfügbar
Konvention: globales Objekt console verfügbar
  • console.log() & console.info() für allgemeine Logausgaben
  • console.warn() für Warnungen
  • console.error() für Fehler
Komplexe Datentypen werden (in der Regel) sinnvoll ausgegeben
  • Voraussetzung: Übergabe als einzelne Parameter
  • Typischerweise JSON, je nach Umgebung allerdings syntaktisch häufig inkorrekt
  • Im Browser: Komplexe Darstellung
  • intro_javascript/hello-world.js
    console.log("Hello World");
    console.log({
      "hello": "world"
    });
    console.log([
      "hello",
      "world"
    ]);
    console.log([1,2],[3,4]);
    
  • Hello World
    { hello: 'world' }
    [ 'hello', 'world' ]
    [ 1, 2 ] [ 3, 4 ]
    

Mathematische Ausdrücke

Werden in Infix-Schreibweise notiert
Wie in C, C++, C#, Java …
Typische mathematische Operationen, allerdings immer mit Fließkommaarithmetik
Nur ein Datentyp number für Zahlen
Inhärent ganzzahlige Operationen konvertieren zu und anschließend von Ganzzahlen
Restklassendivision und Bit-Operationen
  • intro_javascript/expr-1.js
    console.log(39 + 1);  // Addition
    console.log(42 - 1);  // Subtraktion
    console.log(6 * 7);   // Multiplikation
    console.log(100 / 3); // Division
    console.log(100 % 3); // Modulo
    
  • 40
    41
    42
    33.333333333333336
    1
    

String-Ausdrücke

Nutzen den +-Operator zur Konkatenation
Wenn eine Seite ein String ist, wird die andere Seite ebenfalls als String interpretiert
  • intro_javascript/expr-2.js
    console.log("1" + "1");
    console.log("1" + 2);
    console.log("10" + 2);
    console.log("1" + [3]);
    console.log("1" + [4,5]);
    console.log(2 + "3")
    console.log([] + "3")
    console.log("3" + [])
    
  • 11
    12
    102
    13
    14,5
    23
    3
    3
    

Gemischte Ausdrücke

JavaScript wird versuchen eine passende Typkombination zu raten
Häufigster (und gewünschter) Fall: Ein String der sich sinnvoll als Zahl interpretieren lässt
  • intro_javascript/expr-3.js
    console.log("1" + 1);
    console.log("2" - 2);
    console.log("3" * 3);
    console.log("4" / 4);
    console.log("5" - "5");
    console.log("6" * "6");
    console.log("7" / "7");
    
  • 11
    0
    9
    1
    0
    36
    1
    
Häufig taucht der Wert NaN (Not a Number) auf
Dieser Wert zieht sich dann durch alle weiteren Zahlenoperationen
  • intro_javascript/expr-4.js
    console.log("Perry" + 1);
    console.log("Phineas" - 2);
    console.log("Ferb" * 3);
    console.log("Candice" / 4);
    console.log([2, "Doofenschmirtz"] * "Vanessa")
  • Perry1
    NaN
    NaN
    NaN
    NaN
    

Boolesche Ausdrücke: Logische Operatoren

Typische logische Operatoren: ! (Negation), && (logisches “Und”) und || (logisches “Oder”)
Funktionieren für Paare mit identischen Typen erwartungskonform
  • intro_javascript/boolean-logic.js
    console.log(!true);
    console.log(!false);
    console.log(true  && true);
    console.log(true  && false);
    console.log(false && true);
    console.log(false && false);
    console.log(true  || true);
    console.log(true  || false);
    console.log(false || true);
    console.log(false || false);
  • false
    true
    true
    false
    false
    false
    true
    true
    true
    false
    

Boolesche Ausdrücke: Vergleichsoperatoren

Typische Vergleichsoperatoren: ==, !=, >, >=, < und <=
Funktionieren für Paare mit booleschen Typen erwartungskonform
  • intro_javascript/boolean-1.js
    console.log(1 == 1);
    console.log(1 != 1);
    console.log(1 >  1);
    console.log(1 >= 1);
    console.log(1 <  1);
    console.log(1 <= 1);
    console.log("#############");
    console.log("abc" == "xyz");
    console.log("abc" != "xyz");
    console.log("abc" >  "xyz");
    console.log("abc" >= "xyz");
    console.log("abc" <  "xyz");
    console.log("abc" <= "xyz");
  • true
    false
    false
    true
    false
    true
    #############
    false
    true
    false
    false
    true
    true
    

Strikte Vergleichsoperatoren

Problematisch: Einfache Vergleiche nehmen komplizierte Konvertierungen vor, um nach Möglichkeit true zu ergeben
Nur ein Problem bei unterschiedlichen Typen auf der linken und der rechten Seite
  • intro_javascript/boolean-2.js
    console.log('' == '0');
    console.log(0 == '');
    console.log(0 == '0');
    console.log(false == 'false');
    console.log(false == '0');
    console.log(false == undefined);
    console.log(false == null);
    console.log(null == undefined);
    console.log(' \t\r\n ' == 0);
    
  • false
    true
    true
    false
    true
    false
    false
    true
    true
    
Daher: Strikte Vergleichsoperatoren: === und !==
Schlagen bei unterschiedlichen Typen fehl, alle obigen Vergleiche ergäben so false

Nicht transitive Vergleiche, nicht kommutative Operatoren und teils fragwürdige automatische Konvertierungen

(Fast) unendliche Quelle für eine Vielzahl (nerdiger) Witze

Verzweigungen

Auswertung eines booleschen Ausdrucks (if), Alternativen mit else
Wie in C, C++, C#, Java, (Visual Basic), Pascal, …
Neben false noch “falsey”-Werte
  • 0, -0 und NaN
  • Leere Strings ("" und '')
  • null und undefined
  • Im DOM: document.all (leider kein Scherz)
  • intro_javascript/if-1.js
    if ("hello" == "world") {
      console.log("true dat");
    } else {
      console.log("noes");
    }
    
    if (1 > 1) {
      console.log("Boom!")
    } else if (1 < 1) {
      console.log("Bang!");
    } else {
      console.log("Universe intact");
    }
    
  • noes
    Universe intact
    

Vier Zwei Arten von Variablendeklarationen

Die Möglichkeiten:
  • let zur Einführung ab der Erwähnung
  • const zur Einführung von konstanten Werten ab der Erwähnung
  • Historisch: var zur Einführung am Beginn des Funktions-Blocks
  • Ohne Schlüsselwort: Implizit im globalen Namensraum
Globale Zuweisungen und Verwendung von var nur historisch relevant
Alle modernen Umgebungen unterstützen let (caniuse) und const (caniuse)
Für alle Varianten gilt: Angabe eines Typs nicht möglich
Es gilt immer der Typ der letzten Zuweisung
Zulässige Bezeichner beginnen mit Buchstaben, _ oder $, danach auch Ziffern
Groß- und Kleinschreibung ist relevant

Sinnvoll: Einführung von lokalen Variablen mit let

Gültigkeit beginnt ab der Zeile der Deklaration
Wie in C, C++, C#, Java …
Nicht möglich: Angabe eines Typs
  • Jede Variable hat implizit den Typ des zuletzt zugewiesenen Wertes
  • Typen einer Variable können sich folglich zur Laufzeit verändern
  • intro_javascript/let-scoping.js
    let hello = "Hello World";
    console.log(hello);
    hello = ["Hello", "World"];
    console.log(hello);
    
    if (hello) {
      let other = 2;
      console.log(other);
    }
    
    console.log(other);
    
  • Hello World
    [ 'Hello', 'World' ]
    2
    
    [stdin]:11
    console.log(other);
                ^
    
    ReferenceError: other is not defined
        at [stdin]:11:13

Sinnvoll: Einführung von lokalen Konstanten mit const

Gültigkeit beginnt ab der Zeile der Deklaration
Wie in C, C++, C#, Java …
Im Unterschied zu let: Nur einmalige Zuweisung möglich
(Sehr schwache 😐) Garantie, dass sich der Typ nicht verändert
Vorsicht: “Konstantheit” ist nicht transitiv
  • Lediglich der Verweis auf die Variable selbst darf nicht verändert werden, eine Neuzuweisung an den Bezeichner ist also nicht möglich
  • Folge: Die Werte “in” Arrays und Objekten können sich noch verändern
  • Für Informatiker: Wie final in Java, nicht wie const in C++
  • intro_javascript/const-scoping.js
    const num = 12;
    const obj = {
      "key" : "value"
    }
    console.log(obj);
    console.log(num);
    
    // Explizit zulässig
    obj.key = "hello";
    console.log(obj);
    
    // Unzulässig
    obj = { "hello" : "hello" }
    console.log(obj);
    num = num + 1;
    num++;
    
  • { key: 'value' }
    12
    { key: 'hello' }
    
    [stdin]:13
    obj = { "hello" : "hello" }
        ^
    
    TypeError: Assignment to constant variable.
        at [stdin]:13:5

Historisch: Einführung von Variablen mit var

Gültigkeit beginnt dann ab dem jeweiligen Funktionsblock, Zuweisungen werden erst in der “korrekten” Zeile vorgenommen
Bis zur ersten Verwendung ist der Wert undefined
if, for, while, … bilden keinen Block im Sinne der Deklaration mit var
Deklaration der Variable wird vor den Block gezogen
  • undefined
    Hello World
    
  • [stdin]:1
    console.log(hello);
                ^
    
    ReferenceError: hello is not defined
        at [stdin]:1:13
  • intro_javascript/var-scoping-3.js
    console.log("#1 Agent: " + agent);
    console.log("#1 Enemy: " + enemy);
    
    if (agent === "p") {
      var enemy = "doofenschmirtz";
    }
    
    console.log("#2 Agent: " + agent);
    console.log("#2 Enemy: " + enemy);
    
    if (true) {
      var agent = "p";
    }
    
    console.log("#3 Agent: " + agent);
    console.log("#3 Enemy: " + enemy);
    
  • #1 Agent: undefined
    #1 Enemy: undefined
    #2 Agent: undefined
    #2 Enemy: undefined
    #3 Agent: p
    #3 Enemy: undefined
    

Selten nötig, zu Testzwecken aber praktisch: Aktuelle Typen erfragen mit typeof

Operator typeof berechnet einen String der den Typ des übergebenen Ausdrucks angibt
  • Mögliche Resultate sind: undefined, object, boolean, number, string, symbol, function
  • Vorsicht: Es gibt kein Resultat array, der Typ eines Arrays ist object
  • intro_javascript/var-typeof.js
    let myVar = 1;
    console.log("#1", typeof(myVar), myVar);
    
    myVar = myVar + "4";
    console.log("#2", typeof(myVar), myVar);
    
    myVar = myVar / "7";
    console.log("#3", typeof(myVar), myVar);
    
    myVar = [myVar, myVar];
    console.log("#4", typeof(myVar), myVar);
    
    myVar = myVar[2];
    console.log("#5", typeof(myVar), myVar);
  • #1 number 1
    #2 string 14
    #3 number 2
    #4 object [ 2, 2 ]
    #5 undefined undefined
    

Klassische Schleifen

for, while und do while
Wie in C, C++, C#, Java, Pascal, Visual Basic …
Bilden keinen Block im Sinne der Deklaration mit var
Deklaration der Variable wird vorgezogen
  • intro_javascript/loop-classic.js
    for (let i = 0; i < 3; ++i) {
      console.log("for: " + i);
    }
    
    let runner = 8;
    while (runner > 1) {
      runner = runner / 2;
      console.log("while: " + runner);
    }
    
    do {
      runner = runner / 2;
      console.log("do while: " + runner);
    } while (runner > 1);
    
    
  • for: 0
    for: 1
    for: 2
    while: 4
    while: 2
    while: 1
    do while: 0.5
    
Gefährlich: Nutzung von for .. in
Im Rahmen dieser Veranstaltung: Einfach vermeiden

Zugriff auf Arrays

JavaScript-Arrays verhalten sich nicht wie "klassische" Arrays aus Visual Basic, Pascal, Java, C, ... Stattdessen können Arrays (wie Listen) auch nach dem Anlegen noch erweitert werden.
Klassischer Indexzugriff über den []-Operator, gezählt wird von 0 an
Wie in C, C++, C#, Java …
Zuweisungen an Schlüssel zu weit “hinten” erweitern das Array
  • length Eigenschaft wird entsprechend angepasst
  • Unbekannte Werte sind undefined
  • intro_javascript/array-access.js
    const names = ["Phineas", "Ferb"];
    names[3] = "Candice";
    
    for (let i = 0; i < names.length; ++i) {
      console.log("Name #" + i + ": " + names[i]);
    }
    
  • Name #0: Phineas
    Name #1: Ferb
    Name #2: undefined
    Name #3: Candice
    

Hinten anhängen und löschen

Intuitiv: push hängt neue Elemente an das Ende der Liste an
  • Verändert das Array auf dem es aufgerufen wird
  • Ergebnis des Aufrufs ist die neue Länge des Arrays
Intuitiv: pop entfernt Elemente vom Ende der Liste
  • Verändert das Array auf dem es aufgerufen wird
  • Ergebnis des Aufrufs ist das vormals letzte Element
  • intro_javascript/array-insert.js
    const bigIdeas = [
      "Rollercoaster", "Swinter", "X-Ray-Glasses"
    ]
    console.log("Initial", bigIdeas);
    
    const pushResult = bigIdeas.push("Platypus Restaurant");
    console.log("push() result:", pushResult);
    console.log("After  push  :", bigIdeas);
    
    const popResult = bigIdeas.pop();
    console.log("pop() result :", popResult);
    console.log("After append :", bigIdeas);
  • Initial [ 'Rollercoaster', 'Swinter', 'X-Ray-Glasses' ]
    push() result: 4
    After  push  : [ 'Rollercoaster',
      'Swinter',
      'X-Ray-Glasses',
      'Platypus Restaurant' ]
    pop() result : Platypus Restaurant
    After append : [ 'Rollercoaster', 'Swinter', 'X-Ray-Glasses' ]
    

In der Mitte anhängen und löschen

Weniger intuitiv: splice kann an einer bestimmten Stelle sowohl einfügen als auch löschen
  • Signatur: splice(index, deleteCount, items...)
  • Rückgabewert: Array der gelöschten Elemente
  • intro_javascript/array-splice.js
    const bigIdeas = [
      "Rollercoaster", "Swinter", "X-Ray-Glasses"
    ]
    console.log("bigIdeas = ", bigIdeas);
    
    console.log("bigIdeas.splice(0, 1)");
    console.log("return   :", bigIdeas.splice(0, 1));
    console.log("new value:", bigIdeas);
    
    console.log("bigIdeas.splice(1, 0, 'Time machine')");
    console.log("return   :", bigIdeas.splice(1, 0, 'Time machine'));
    console.log("new value:", bigIdeas);
    
    console.log("bigIdeas.splice(1, 2, 'Rocket')");
    console.log("return   :", bigIdeas.splice(1, 2, 'Rocket'));
    console.log("new value:", bigIdeas);
  • bigIdeas =  [ 'Rollercoaster', 'Swinter', 'X-Ray-Glasses' ]
    bigIdeas.splice(0, 1)
    return   : [ 'Rollercoaster' ]
    new value: [ 'Swinter', 'X-Ray-Glasses' ]
    bigIdeas.splice(1, 0, 'Time machine')
    return   : []
    new value: [ 'Swinter', 'Time machine', 'X-Ray-Glasses' ]
    bigIdeas.splice(1, 2, 'Rocket')
    return   : [ 'Time machine', 'X-Ray-Glasses' ]
    new value: [ 'Swinter', 'Rocket' ]
    

Zugriff auf alle Schlüssel und Werte eines Objekts

Aufzählen von Schlüssen oder Werten mit Funktionen aus Object
  • Object.keys liefert ein Array aller Schlüssel des übergebenen Objektes
  • Object.values liefert ein Array aller Werte des übergebenen Objektes
Ein Objekt könnte beliebige Schlüssel haben, auch keys und values. Daher müssen, anders als bei Arrays, spezielle Objekt-"Methoden" als Funktionen des "Object-Objektes" implementiert werden.
  • intro_javascript/object-functions.js
    const homes = {
      "phineas": "Maple Drive 2308",
      "ferb": "Maple Drive 2308",
      "doofenschmirtz": "Doofenshmirtz Evil Inc."
    }
    
    console.log(Object.keys(homes));
    console.log(Object.values(homes));
  • [ 'phineas', 'ferb', 'doofenschmirtz' ]
    [ 'Maple Drive 2308',
      'Maple Drive 2308',
      'Doofenshmirtz Evil Inc.' ]
    

Zugriff auf bestimmte Werte eines Objekts

Wie bei Arrays: []-Operator mit Angabe eines Schlüssels
Typ des Schlüssels ist immer string
Zusätzlich zum []-Operator: Punktnotation
Wie in C-struct, Java-Klassen, Pascal record, Visual Basic Structure, …
Einfügen von neuen Schlüsseln per Zuweisung
Wie bei Map in Java, std::map in C++, …
  • intro_javascript/object-access.js
    const homes = {
      "phineas": "Maple Drive 2308",
      "ferb": "Maple Drive 2308",
      "doofenschmirtz": "Doofenshmirtz Evil Inc."
    }
    
    homes.isabella = "across from the Flynn-Fletcher house";
    homes["Major Monogram"] = "O.W.C.A. Secret Headquarters";
    
    const keys = Object.keys(homes);
    for (let i = 0; i < keys.length; ++i) {
      const key = keys[i];
      console.log(key, "=>", homes[key]);
    }
    
  • phineas => Maple Drive 2308
    ferb => Maple Drive 2308
    doofenschmirtz => Doofenshmirtz Evil Inc.
    isabella => across from the Flynn-Fletcher house
    Major Monogram => O.W.C.A. Secret Headquarters
    

Löschen aus Objekten

Operator delete zum Löschen von Einträgen
  • Technisch gesehen: Angabe des zu löschenden Schlüssels
  • Syntaktisch gesehen: Angabes des zu löschenden Wertes im Objekt
  • intro_javascript/object-delete.js
    const homes = {
      "phineas": "Maple Drive 2308",
      "ferb": "Maple Drive 2308",
      "doofenschmirtz": "Doofenshmirtz Evil Inc."
    }
    
    delete homes.ferb;
    delete homes['phineas'];
    
    console.log(homes);
  • { doofenschmirtz: 'Doofenshmirtz Evil Inc.' }
    

Template-Literals

Seit ECMA-Script 6: Einbettung von Ausdrücken in Strings
Praktische abkürzende Schreibweise ohne möglicherweise aus Versehen auf das mathematische + zurückzufallen
Syntax: Notation des Strings in “backticks” (französischer accent grave): `Template-String`
  • Erlaubt Zeilenumbrüche in Strings, Leerzeichen werden dabei aber exakt wiedergegeben
  • Andere Anführungszeichen können ebenfalls genutzt werden
Syntax: Notation von Ausdrücken in Template-Strings mit `${ expr }`
Ausdruck hat Zugriff auf gesamte Umgebung
  • intro_javascript/template-literals.js
    console.log(`2 + 2 = ${2 + 2}`);
    console.log(
      `Zeile 1
       Zeile 2`
    );
    
    const person = {
      "name": "Phineas",
      "hair": "orange"
    }
    
    console.log(`"${person.name}" has ${person.hair} hair`);
    
  • 2 + 2 = 4
    Zeile 1
       Zeile 2
    "Phineas" has orange hair
    

Can I Use template-literals? Data on support for the template-literals feature across the major browsers from caniuse.com.

Die Standardbibliothek

JavaScript kommt mit einer recht typischen Standardbibliothek
Zusätzlich Erweiterungen für Browser oder Server verfügbar
Einige Inhalte werden hier nicht explizit besprochen, könnten aber hilfreich sein:
  • Die Math-Bibliothek mit Konstanten wie Math.PI oder Funktionen wie Math.max, Math.pow, Math.random, Math.sin, …
  • String-“Funktionen” sind Methoden des String-Objekts und haben typische Funktionalität wie startsWith, substr, trim, toUpperCase, …
  • Datums-“Funktionen” sind Methoden des Date-Objekts und haben typische Funktionalität wie getHours, getMonth, … Vorsicht: Benutzen Sie nicht getYear, sondern stattdessen getFullYear

Umgang mit JSON-Daten

Zwei wesentliche Funktionen:
  • JSON.parse wandelt einen String in die entsprechende Datenstruktur
  • JSON.stringify wandelt eine Datenstruktur in die entsprechende JSON-Stringdarstellung
  • intro_javascript/json.js
    const data = JSON.parse('{ "name": "Phineas" }');
    console.log("Parsed Name:", data.name);
    console.log("Parsed Data:", data);
    const dataString = JSON.stringify(data);
    console.log("Stringified:", dataString);
    
  • Parsed Name: Phineas
    Parsed Data: { name: 'Phineas' }
    Stringified: {"name":"Phineas"}
    

Funktionen

Nehmen Parameter entgegen, berechnen Werte und bilden einen Block
Wie in C, C++, C#, Java, Pascal, Visual Basic …
Syntax zur Deklaration: function name(param1, ...)
Wie bei Variablen: Keine Typangaben möglich
Ähnlich wie var: Deklaration und Implementierung wird vorgezogen
Funktionen können also vor ihrer Deklaration Quelltext verwendet werden
Nicht auf diese Umsortierung verlassen! Hohes Maß an Fehleranfälligkeit, wenn die Umsortierung durch den Compiler eine Rolle spielt.
  • intro_javascript/function-simple.js
    greet("Phineas");
    greet("Ferb");
    greet(["Phineas", "Ferb"]);
    greet(true);
    
    console.log("Typ von greet:", typeof (greet));
    
    function greet(name) {
      console.log("Hallo " + name);
    }
    
  • Hallo Phineas
    Hallo Ferb
    Hallo Phineas,Ferb
    Hallo true
    Typ von greet: function
    

Ergebnisse von Funktionen

Wie bei Parametern & Variablen: Keine Typangaben
  • Eine Funktion kann jederzeit jeden Typ berechnen
  • Wenn eine Funktion nichts berechnet ist der Wert undefined
Schlüsselwort return beendet einen Funktionsuafruf und gibt einen Wert zurück
  • Wie in C, C++, C#, Java
  • In Pascal & Visual Basic: Definition des Rückgabewerts über Zuweisung an den Funktionsnamen
  • intro_javascript/function-return.js
    function isAwesome(name) {
      if (name === "Clara" || name === "Adele") {
        return ("awesome");
      }
    
      if (name.length % 5 === 0) {
        return ("nope");
      }
    
      return (name[0] === "S");  
    }
    
    console.log(isAwesome("Clara"));
    console.log(isAwesome("Mario"));
    console.log(isAwesome("Dario"));
    console.log(isAwesome("Marcus"));
    console.log(isAwesome("Solvey"));
  • awesome
    nope
    nope
    false
    true
    

Anonyme Funktionen / Funktionen höherer Ordnung

Funktionen müssen keinen Namen haben und können wie “normale” Daten übergeben werden
Ergo: Zuweisung an Variablen möglich
Syntax zur Deklaration anonymer Funktionen: function (param1, ...)
Wie bei Variablen: Keine Typangaben möglich
An dieser Stelle hilfreich: Deklaration mit const
Hinterlegte Funktion kann dann nicht mehr überschrieben werden
  • intro_javascript/function-higher-order.js
    const greet = function(name) {
      console.log("Hallo " + name);
    }
    
    greet("Phineas");
    greet("Ferb");
    greet(["Phineas", "Ferb"]);
    greet(true);
    
    console.log("Typ von greet:", typeof (greet));
    
  • Hallo Phineas
    Hallo Ferb
    Hallo Phineas,Ferb
    Hallo true
    Typ von greet: function
    

Primitive Typen und Referenztypen

Primitive Typen verhalten sich wie Werte
Wie in C# oder Java
Primitive Werte sind:
  • true und false, also boolean
  • null und undefined, also “fehlende” Werte
  • string, also Zeichenketten
  • number, also Zahlen
  • symbol (Teil der fortgeschrittenen Veranstaltung)
Alle anderen Typen (konkret: object) verhalten sich wie Referenzen
Wie in C# oder Java

Beispiel zu Parameterübergabe

  • intro_javascript/function-reference-params.js
    function add1(num) {
      num++;
    }
    
    function append1(arr) {
      arr.push(1);
    }
    
    function change1(obj) {
      obj.one = "changed";
    }
    
    const myNum = 0;
    console.log("number before add1:   ", myNum);
    add1(myNum);
    console.log("number after  add1:   ", myNum);
    
    const myArray = [0];
    console.log("Array  before append1:", myArray);
    append1(myArray);
    console.log("Array  after  append1:", myArray);
    
    const myObj = { "one": "inital" };
    console.log("Object before change1:", myObj);
    change1(myObj);
    console.log("Object after  change1:", myObj);
  • number before add1:    0
    number after  add1:    0
    Array  before append1: [ 0 ]
    Array  after  append1: [ 0, 1 ]
    Object before change1: { one: 'inital' }
    Object after  change1: { one: 'changed' }
    

Defensiv programmieren mit typeof

Grundidee: Das Programm lieber krachend beenden als möglicherweise etwas falsches zu tun
Werfen einer Ausnahme als einfachster “Notausstieg”
Problem: Funktioniert nur gut für primitive Typen
Komplexe Typen haben den typeof-Wert object und lassen sich nicht gut inspizieren
Problem: Erfordert eine ganze Menge “langweiligen” Code …
… der im produktiven Betrieb niemals fehlschlagen sollte!
Die stark verwandte Sprache TypeScript löst die Typprobleme eleganter und wird im Rahmen der fortgeschrittenen "Web-Anwendungen" behandelt.
  • intro_javascript/function-defensive.js
    function calculatePrice(singlePrice, quantity, discount) {
      if (typeof singlePrice !== "number") {
        throw "singlePrice must be a number";
      }
      if (typeof quantity !== "number") {
        throw "quantity must be a number";
      }
      if (typeof discount !== "boolean") {
        throw "discount must be a boolean";
      }
    
      let toReturn = singlePrice * quantity;
      if (discount) {
        toReturn = toReturn * 0.85;
      }
    
      return (toReturn);
    }
    
    console.log(calculatePrice(10, 0.35, true));
    console.log(calculatePrice([], {}, [true]));
  • 2.975
    
    
    [stdin]:3
        throw "singlePrice must be a number";
        ^
    singlePrice must be a number

Zugriff auf die Argumentliste mit arguments

Formale Parameter einer Funktion sind lediglich ein “Vorschlag”
Aktuelle Parameter können davon beliebig abweichen
Problem: Zugriff auf überzählige Parameter
Notwendig zur Implementierung von z.B. Array.splice
Lösung: Variable arguments für jeden Aufruf vordefiniert
Enthält alle aktuellen Parameter in Form eines Objektes (das sich wie ein Array verhält)
Vorsicht: Übermäßig “kreative” Lösungen können arguments überschreiben
Ist kein Schlüsselwort der Sprache
  • javascript_advanced/arguments.js
    const myFunc = function(p1, p2, p3) { 
      console.log(`p1 = ${p1}, p2 = ${p2}, p3 = ${p3}`);  
      console.log(arguments);
    }
    
    myFunc();
    myFunc("a");
    myFunc("a", "b");
    myFunc("a", "b", "c");
    myFunc("a", "b", "c", "d");
    
  • p1 = undefined, p2 = undefined, p3 = undefined
    [Arguments] {}
    p1 = a, p2 = undefined, p3 = undefined
    [Arguments] { '0': 'a' }
    p1 = a, p2 = b, p3 = undefined
    [Arguments] { '0': 'a', '1': 'b' }
    p1 = a, p2 = b, p3 = c
    [Arguments] { '0': 'a', '1': 'b', '2': 'c' }
    p1 = a, p2 = b, p3 = c
    [Arguments] { '0': 'a', '1': 'b', '2': 'c', '3': 'd' }
    

Typische Verwendung: Optionale Parameter

Grundidee: Nicht immer alle Parameter angeben müssen
Standardwerte für nicht übergebene Parameter hinterlegen
  • javascript_advanced/arguments-optional.js
    const writeToLog = function(text, level, time) {
      if (typeof (level) === "undefined") {
        level = "INFO";
      }
      
      if (typeof (time) === "undefined") {
        time = new Date();
      }
      
      console.log(`[${level}] ${time.toTimeString()}: ${text}`);
    };
    
    writeToLog("Test");
    writeToLog("Oops", "WARN");
    writeToLog("Past", "ERR ", new Date('1995-05-17T03:24:00'));
    
  • [INFO] 10:52:42 GMT+0200 (Central European Summer Time): Test
    [WARN] 10:52:42 GMT+0200 (Central European Summer Time): Oops
    [ERR ] 03:24:00 GMT+0200 (Central European Summer Time): Past
    

Typische Verwendung: Überladene Funktionen

Aufgrund der dynamischen Typisierung: Beliebige übergabe von Daten an Funktionen
Kann für polymorphe Daten auch zum Vorteil genutzt werden
Beispiel: Entgegennehmen von Objekten oder ihren IDs
Häufig genutzt im Kontext von Datenbankanwendungen
  • javascript_advanced/function-overload.js
    const addStarToUser = function(user) {
      if (typeof(user) === 'number' || typeof(user) === 'string') {
        console.log(`Transparently retrieving user #${user}`);
        user = getUser(user);
      }
    
      user.stars++;
    };
    
    console.log(getUser(1));
    addStarToUser(1);
    const user = getUser(1);
    addStarToUser(user);
    console.log(getUser(1));
    
  • { id: 1, username: 'Scripty McScriptFace', stars: 1023 }
    Transparently retrieving user #1
    { id: 1, username: 'Scripty McScriptFace', stars: 1025 }
    

Bevorzugt: Iteration mit Funktionen

Namen der Iterations-Funktionen erlauben Rückschluss über den Zweck der Iteration
for und while geben keinen Aufschluss über den Zweck der Schleife
Ohne Veränderung des originalen Arrays
Erzeugen stattdessen ein neues Array und beugen so Fehlern vor
Lassen sich hervorragend kombinieren
Funktionen sind komponierbare Bausteine
Funktionsweise: Übergabe einer Funktion als “Variable”
  • Diese so genannte “Callback-Funktion” wird dann immer wieder aufgerufen
  • Die meisten Callback-Funktionen sollten bis zu drei Parameter entgegennehmen
    1. currentValue, das aktuelle Element, entspricht array[index]
    2. index, der Index des aktellen Elements
    3. array, das originale Array
Abseits von Iterationsfunktionen: Callbacks für Benutzerinteraktion
Hinterlegen einer Funktion die ausgeführt wird, wenn z.B. ein Knopf gedrückt wird

Klassische Iteration mit forEach

Zweck: Iteration über Array, ohne dabei ein Ergebnis zu berechnen
Typischster Fall: Ausgabe
Ist keine Funktion, sondern eine Prozedur
Ergebnis ist immer undefined
  • intro_javascript/loop-array-foreach.js
    const myPrint = function(v, i) {
      console.log("Wert", v, "an Index", i);
    };
    
    ["Phineas", "Ferb"].forEach(myPrint);
    [1, 1, 0].forEach(myPrint);
    [
      [1, 0, 0],
      [0, 1, 0],
      [0, 0, 1]
    ].forEach(myPrint);
    
  • Wert Phineas an Index 0
    Wert Ferb an Index 1
    Wert 1 an Index 0
    Wert 1 an Index 1
    Wert 0 an Index 2
    Wert [ 1, 0, 0 ] an Index 0
    Wert [ 0, 1, 0 ] an Index 1
    Wert [ 0, 0, 1 ] an Index 2
    

Beispielhafte Implementierungen

Im Folgenden: Immer wieder mal beispielhafte Implementierung von Iterationsfunktionen
Bitte keinesfalls in realen Projekten verwenden
Zweck: “Magische” Funktionen entmystifizieren
Hilft beim Verständnis von “Funktionen als Daten”
Marginal andere Aufrufsyntax: 1. Argument ist zu iterierendes Array (nicht objektorientiert)
Anders als die “echten Funktionen” nicht als Methode implementiert

Oberflächliche Implementierung von forEach

Vollständige und korrekte Implementierung im MDN
Verwendet fortgeschrittene Techniken und zeigt viele technische Details
  • intro_javascript/impl-foreach.js
    const forEach = function(arr, func) {
      for (let i = 0; i < arr.length; i++) {
        func(arr[i], i, arr);
      }
    }
    
    console.log("Aufruf 1");
    forEach(["heinz","isabella"], console.log);
    
    console.log("Aufruf 2");
    forEach([true, { "agent": "p" }, 3], function(value, key) {
      console.log(`arr[${key}] = `, value);
    });
  • Aufruf 1
    heinz 0 [ 'heinz', 'isabella' ]
    isabella 1 [ 'heinz', 'isabella' ]
    Aufruf 2
    arr[0] =  true
    arr[1] =  { agent: 'p' }
    arr[2] =  3
    

Finden von Werten mit find

Zweck: Suchen eines Elementes anhand eines Prädikates
Erstes Element, für das true berechnet wird, ist dann das Ergebnis von find
  • intro_javascript/loop-array-find.js
    const complex_data = [
      { name: "Phineas", hair: "red" },
      { name: "Ferb", hair: "green" },
      { name: "Agent P", hair: "green" }
    ];
    
    const greenHair = function(v) {
      return (v.hair === "green");
    };
    
    const nameStartsWithA = function(v) {
      return (v.name[0] === "A" || v.name[0] === "a");
    };
    
    console.log(complex_data.find(greenHair));
    console.log(complex_data.find(nameStartsWithA));
    
  • { name: 'Ferb', hair: 'green' }
    { name: 'Agent P', hair: 'green' }
    

Oberflächliche Implementierung von find

Vollständige und korrekte Implementierung im MDN
Verwendet fortgeschrittene Techniken und zeigt viele technische Details
  • intro_javascript/impl-find.js
    const find = function(arr, func) {
      for (let i = 0; i < arr.length; i++) {
        if (func(arr[i], i, arr)) {
          return (arr[i]);
        }
      }
    
      return (undefined);
    }
    
    console.log(find([1,2], function(v) { return (v == 1); }));
    console.log(find([1,2], function(v) { return (v >= 1); }));
    console.log(find([1,2], function(v) { return (v >= 2); }));
    console.log(find([1,2], function(v) { return (v >  2); }));
    
    
  • 1
    1
    2
    undefined
    

Praktischer Exkurs: Arrow-Notation für Funktionen

Permanente Erwähnung von function, return und geschweiften Klammern ist mühselig
Und für später: Hat Einfluss auf den this-Kontext
(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
  • => statt function, Argumente werden davor notiert
  • Bei nur einem Argument: Klammern optional
  • Bei keinem Argument: () oder _ angeben
  • Bei exakt einem Ausdruck im Rumpf: Geschweifte Klammern und return optional
  • intro_javascript/function-short-syntax.js
    const complex_data = [
      { name: "Phineas", hair: "red" },
      { name: "Ferb", hair: "green" },
      { name: "Agent P", hair: "green" }
    ];
    
    complex_data.forEach(v => console.log(v.name));
    console.log(complex_data.find(v => v.name === "Phineas"));
    
  • Phineas
    Ferb
    Agent P
    { name: 'Phineas', hair: 'red' }
    

Filtern von Werten mit filter

Zweck: Entfernen von unerwünschten Werten aus dem Array
Resultat ist ein neues Array
  • intro_javascript/loop-array-filter.js
    const complex_data = [
      { name: "Phineas", hair: "red" },
      { name: "Ferb", hair: "green" },
      { name: "Agent P", hair: "green" },
      { name: "Candice", hair: "blonde" },
    ]
    console.log(
      "Greenheads:\n",
      complex_data.filter(v => v.hair === "green")
    );
    console.log(
      "Non Greenheads:\n",
      complex_data.filter(v => v.hair !== "green")
    );
    console.log(
      "Short Names:\n",
      complex_data.filter(v => v.name.length < 7)
    );
    console.log(
      "Long Names:\n",
      complex_data.filter(v => v.name.length > 7)
    );
    
  • Greenheads:
     [ { name: 'Ferb', hair: 'green' },
      { name: 'Agent P', hair: 'green' } ]
    Non Greenheads:
     [ { name: 'Phineas', hair: 'red' },
      { name: 'Candice', hair: 'blonde' } ]
    Short Names:
     [ { name: 'Ferb', hair: 'green' } ]
    Long Names:
     []
    

Oberflächliche Implementierung von filter

Vollständige und korrekte Implementierung im MDN
Verwendet fortgeschrittene Techniken und zeigt viele technische Details
  • intro_javascript/impl-filter.js
    const filter = function(arr, func) {
      const toReturn = [];
      for (let i = 0; i < arr.length; i++) {
        if (func(arr[i], i, arr)) {
          toReturn.push(arr[i]);
        }
      }
      return (toReturn);
    }
    
    console.log(filter([1,2,3,4], v => v >= 1));
    console.log(filter([1,2,3,4], v => v >  1));
    console.log(filter([1,2,3,4], v => v == 2));
  • [ 1, 2, 3, 4 ]
    [ 2, 3, 4 ]
    [ 2 ]
    

Prüfen von Eigenschaften mit some und every

Zweck: Prüfen ob mindestens eins (some) oder alle (every) Elemente eines Arrays ein Prädikat erfüllen
Entspricht dem logischen OR bzw. dem logischen AND
  • intro_javascript/loop-array-some-every.js
    const complex_data = [
      { name: "Phineas", hair: "red" },
      { name: "Ferb", hair: "green" },
      { name: "Agent P", hair: "green" },
      { name: "Candice", hair: "blonde" },
    ]
    
    console.log("Alle grüne Haare?",
                complex_data.every(p => p.hair === "green"));
    
    console.log("Irgendwer grüne Haare?",
                complex_data.some(p => p.hair === "green"));
  • Alle grüne Haare? false
    Irgendwer grüne Haare? true
    

Oberflächliche Implementierung von some und every (I)

Vollständige und korrekte Implementierung im MDN
Verwendet fortgeschrittene Techniken und zeigt viele technische Details
  • intro_javascript/impl-some-every-slow.js
    const some = function(arr, func) {
      return (arr.filter(func).length >= 1);
    }
    
    const every = function(arr, func) {
      return (arr.filter(func).length == arr.length);
    }
    
    const onlyThree = v => v == 3;
    
    console.log([1,2,3]);
    console.log("Enthält 3?", some([1,2,3], onlyThree));
    console.log("Nur 3?", every([1,2,3], onlyThree));
    
    console.log([3]);
    console.log("Enthält 3?", some([3], onlyThree));
    console.log("Nur 3?", every([3], onlyThree));
    
    console.log([1]);
    console.log("Enthält 3?", some([1], onlyThree));
    console.log("Nur 3?", every([1], onlyThree));
  • [ 1, 2, 3 ]
    Enthält 3? true
    Nur 3? false
    [ 3 ]
    Enthält 3? true
    Nur 3? true
    [ 1 ]
    Enthält 3? false
    Nur 3? false
    

Oberflächliche Implementierung von some und every (II)

Vollständige und korrekte Implementierung im MDN
Verwendet fortgeschrittene Techniken und zeigt viele technische Details
  • intro_javascript/impl-some-every.js
    const some = function(arr, func) {
      for (let i = 0; i < arr.length; i++) {
        if (func(arr[i], i, arr)) {
          return (true);
        }
      }
      return (false);
    }
    
    const every = function(arr, func) {
      for (let i = 0; i < arr.length; i++) {
        if (!func(arr[i], i, arr)) {
          return (false);
        }
      }
      return (true);
    }
    
    const onlyThree = v => v == 3;
    
    console.log([1,2,3]);
    console.log("Enthält 3?", some([1,2,3], onlyThree));
    console.log("Nur 3?", every([1,2,3], onlyThree));
    
    console.log([3]);
    console.log("Enthält 3?", some([3], onlyThree));
    console.log("Nur 3?", every([3], onlyThree));
    
    console.log([1]);
    console.log("Enthält 3?", some([1], onlyThree));
    console.log("Nur 3?", every([1], onlyThree));
  • [ 1, 2, 3 ]
    Enthält 3? true
    Nur 3? false
    [ 3 ]
    Enthält 3? true
    Nur 3? true
    [ 1 ]
    Enthält 3? false
    Nur 3? false
    

Veränderung oder Extraktion von Werten mit map

Zweck: Veränderung jedes einzelnen Wertes im Array
Resultat ist ein neues Array
  • [ 2, 3, 4 ]
    [ 'Remote Control-inator',
      'Audience Control-inator',
      'Cute-Puppy-Call-inator' ]
    

Oberflächliche Implementierung von map

Vollständige und korrekte Implementierung im MDN
Verwendet fortgeschrittene Techniken und zeigt viele technische Details
  • intro_javascript/impl-map.js
    const map = function(arr, func) {
      const toReturn = [];
      for (let i = 0; i < arr.length; i++) {
        toReturn.push(func(arr[i]));
      }
      return (toReturn);
    }
    
    console.log(map([1,2,3], v => v + 1));
    console.log(map([1,2,3], v => v * 3));
    console.log(map(
      ["Kind", "Eimer", "Bagger", "Massaker", "Regenschirm"],
      v => v + "chen"
    ));
  • [ 2, 3, 4 ]
    [ 3, 6, 9 ]
    [ 'Kindchen',
      'Eimerchen',
      'Baggerchen',
      'Massakerchen',
      'Regenschirmchen' ]
    

map und Extraktion aus Objekten

  • intro_javascript/loop-array-map.js
    const extractName = o => o.name;
    const length      = s => s.length;
    
    const complex_data = [
      { name: "Phineas", hair: "red" },
      { name: "Ferb", hair: "green" },
    ]
    
    console.log(
      complex_data
        .map(extractName)
    );
    console.log(
      complex_data
        .map(extractName)
        .map(length)
    );
    console.log(
      complex_data
        .map(extractName)
        .map(length)
        .map(v => v % 2 === 0)
    );
    
  • [ 'Phineas', 'Ferb' ]
    [ 7, 4 ]
    [ false, true ]
    

map und Umgang mit Objekten

Problematisch: map ersetzt möglicherweise ein komplexes Objekt durch einen einzigen Wert
Erschwert Kombination unterschiedlicher Primitiven
  • intro_javascript/loop-array-map-copy.js
    const complex_data = [
      { name: "Matt", hair: "brown", born: 1978 },
      { name: "Chris", hair: "brown", born: 1978 },
      { name: "Dominic", hair: "blonde", born: 1977 },
    ]
    
    const currentAge = function(person) {
      let currentYear = new Date().getFullYear();
      person.age = (currentYear - person.born);
      return (person);
    }
    
    console.log(
      "Mapped Data:\n",
      complex_data.map(currentAge));
    console.log(
      "Original Data:\n",
      complex_data
    );
    
  • Mapped Data:
     [ { name: 'Matt', hair: 'brown', born: 1978, age: 41 },
      { name: 'Chris', hair: 'brown', born: 1978, age: 41 },
      { name: 'Dominic', hair: 'blonde', born: 1977, age: 42 } ]
    Original Data:
     [ { name: 'Matt', hair: 'brown', born: 1978, age: 41 },
      { name: 'Chris', hair: 'brown', born: 1978, age: 41 },
      { name: 'Dominic', hair: 'blonde', born: 1977, age: 42 } ]
    

map und tiefe Kopien von Objekten

Lösungsansatz: Vor jeder Veränderung eine Kopie der Eingabe erstellen
Funktioniert nicht bei Objekten mit zirkulären Referenzen
  • intro_javascript/loop-array-map-deep-copy.js
    const complex_data = [
      { name: "Matt", hair: "brown", born: 1978 },
      { name: "Chris", hair: "brown", born: 1978 },
      { name: "Dominic", hair: "blonde", born: 1977 },
    ]
    
    const deepCopy = function(obj) {
      // Nicht-so-eleganter-Hack
      return (JSON.parse(JSON.stringify(obj)));
    }
    
    const currentAge = function(person) {
      let currentYear = new Date().getFullYear();
      person.age = (currentYear - person.born);
      return (person);
    }
    
    console.log(
      "Mapped Data:\n",
      complex_data
        .map(deepCopy)
        .map(currentAge));
    console.log(
      "Original Data:\n",
      complex_data
    );
    
  • Mapped Data:
     [ { name: 'Matt', hair: 'brown', born: 1978, age: 41 },
      { name: 'Chris', hair: 'brown', born: 1978, age: 41 },
      { name: 'Dominic', hair: 'blonde', born: 1977, age: 42 } ]
    Original Data:
     [ { name: 'Matt', hair: 'brown', born: 1978 },
      { name: 'Chris', hair: 'brown', born: 1978 },
      { name: 'Dominic', hair: 'blonde', born: 1977 } ]
    

Arrays aggregieren mit reduce

Zweck: Verdichtet ein Array von Werten auf einen einzigen Wert
Typische Spezialisierungen: sum, count, min, max
Veränderte Callback-Signatur: Zusätzlicher erster Parameter
  1. akkumulator, das Ergebnis der vorherigen Berechnung
  2. currentValue, das aktuelle Element, entspricht array[index]
  3. index, der Index des aktellen Elements
  4. array, das originale Array
Veränderte Aufruf-Signatur: reduce(callback, [initialValue])
Fungiert als initialer Akkumulator, Wird kein initialValue übergeben, wird der erste Wert des Arrays verwendet
Der berechnete Wert jedes Aufrufs der Callback-Funktion fungiert als Akkumulator für den nächsten Aufruf
Das Ergebnis des letzten Aufrufs der Callback-Funktion ist dann das Ergebnis des Aufrufs von reduce

Ausführliches Beispiel zu reduce

Dieser Aufruf von reduce ruft seinerseits die übergebene Callback-Funktion vier Mal auf
Den vier Aufrufen werden dabei die folgenden Werte übergeben:
accumulator currentValue currentIndex array return
4 1 1 [4,1,3,5,2] 5
5 3 2 [4,1,3,5,2] 8
8 5 3 [4,1,3,5,2] 13
13 2 4 [4,1,3,5,2] 15
Vorsicht: currentIndex beginnt in diesem Beispiel bei 1
Der Wert an Index 0 ist der initiale Wert für den Akkumulator, da kein separater Wert übergeben worden ist.

Kurze Beispiele zu reduce

  • intro_javascript/loop-array-reduce.js
    const size = (akku, _) => akku + 1;
    const add  = (lhs, rhs) => lhs + rhs;
    const mult = (lhs, rhs) => lhs * rhs;
    
    const numbers = [24,12,46,1,0,4,7,2];
    
    console.log(numbers.reduce(size));
    console.log(numbers.reduce(size, 0));
    console.log(numbers.reduce(add));
    console.log(numbers.reduce(mult))
    
  • 31
    8
    96
    0
    

Oberflächliche Implementierung von reduce (I)

Vollständige und korrekte Implementierung im MDN
Verwendet fortgeschrittene Techniken und zeigt viele technische Details
  • intro_javascript/impl-reduce.js
    const reduce = function(arr, func, init) {
      let accu = init;
    
      for(let i =0; i < arr.length; i++) {
        accu = func(accu, arr[i]);
      }
    
      return (accu);
    }
    
    console.log("+ mit 0:", reduce([1,2,3], (a, v) => a + v, 0));
    console.log("+ mit 1:", reduce([1,2,3], (a, v) => a + v, 1));
    
    console.log("* mit 0:", reduce([1,2,3], (a, v) => a * v, 0));
    console.log("* mit 1:", reduce([1,2,3], (a, v) => a * v, 1));
  • + mit 0: 6
    + mit 1: 7
    * mit 0: 0
    * mit 1: 6
    

Oberflächliche Implementierung von reduce (II)

Vollständige und korrekte Implementierung im MDN
Verwendet fortgeschrittene Techniken und zeigt viele technische Details
  • intro_javascript/impl-reduce-overload.js
    const reduce = function(arr, func, init) {
      let i = 0;
      let accu = init;
      if (typeof init === "undefined")
      {
        accu = arr[0];
        i = 1;
      }
    
      for(;i < arr.length; i++) {
        accu = func(accu, arr[i]);
      }
    
      return (accu);
    }
    
    console.log("+ mit 0:", reduce([1,2,3], (a, v) => a + v, 0));
    console.log("+ ohne :", reduce([1,2,3], (a, v) => a + v));
    
    console.log("* mit 0:", reduce([1,2,3], (a, v) => a * v, 0));
    console.log("* ohne :", reduce([1,2,3], (a, v) => a * v));
  • + mit 0: 6
    + ohne : 6
    * mit 0: 0
    * ohne : 6
    

Kombination von filter und reduce

  • intro_javascript/loop-array-filter-reduce.js
    const add  = (lhs, rhs) => lhs + rhs;
    const mult = (lhs, rhs) => lhs * rhs;
    const isEven = (val) => val % 2 === 0;
    
    const numbers = [24,12,46,1,0,4,7,2];
    
    console.log(
      numbers
        .filter(isEven)
        .reduce(add)
    );
    console.log(
      numbers
        .filter(isEven)
        .reduce(mult)
    );
    console.log(
      numbers
        .filter(isEven)
        .filter(v => v > 0)
        .reduce(mult)
    );
    console.log(
      numbers
        .filter(v => v % 2 === 0 && v > 0)
        .reduce(mult)
    );
    
  • 88
    0
    105984
    105984
    

Kombination von map und reduce (I)

map zur Extraktion des Wertes costs aus den komplexen Daten
Bewusster Informationsverlust
  • intro_javascript/loop-array-map-reduce.js
    const add = (lhs, rhs) => lhs + rhs;
    
    const complex_data = [
      { name: "Remote Control", costs: 4 },
      { name: "Audience Control", costs: 7 },
      { name: "Cute-Puppy-Call", costs: 8 },
    ]
    
    console.log(
      complex_data
        .map(v => v.costs)
        .reduce(add)
    );
    
  • 19
    

Kombination von map und reduce (II)

map berechnet einen abgeleiteten Wert
Menge * Preis
  • intro_javascript/loop-array-map-reduce-intermediate.js
    const add = (lhs, rhs) => lhs + rhs;
    
    const complex_data = [
      { name: "Remote Control", costs: 4, amount: 2 },
      { name: "Audience Control", costs: 7, amount: 3 },
      { name: "Cute-Puppy-Call", costs: 8, amount: 1 },
    ]
    
    console.log(
      complex_data
        .map(v => v.costs * v.amount)
        .reduce(add)
    );
    
  • 37
    

Kombination von map und reduce (III)

map berechnet einen abgeleiteten Wert
Summe aller Preise in einer Liste (wiederrum mit reduce)
  • intro_javascript/loop-array-map-reduce-complex.js
    const add = (lhs, rhs) => lhs + rhs;
    
    const complex_data = [
      { name: "Remote Control", costs: [4, 12, 25] },
      { name: "Audience Control", costs: [65, 62] },
      { name: "Cute-Puppy-Call", costs: [32, 78] },
    ]
    
    console.log(
      complex_data
        .map(v => v.costs.reduce(add))
        .reduce(add)
    );
    
  • 278
    

Mit ein bisschen Übung: Iterationsfunktionen schreiben sich wie SQL

Einige Operationen mit unmittelbaren Entsprechungen
  • filter entspricht where
  • map entspricht select
  • reduce entspricht sum, min, max, …
Der wesentliche Vorteil dieser Funktionen ist die bessere Lesbarkeit! Im Gegensatz zu Schleifen ist der Zweck jeder Iteration klar definiert.
Schlagwort LINQ: “Language Integrated Queries”
SQL-artige Anweisungen in Sprachen wie C# funktionieren intern exakt wie hier gezeigt
Fehlende Operationen kann man sich auch selber schreiben
Wie gezeigt: Eigene Iterationsfunktionen sind kein Hexenwerk

Neue Primitive: groupBy

Gewünschtes Ergebnis: Kategorisierung von Werten
  • Callback-Funktion extrahiert den relevanten Wert aus dem Objekt
  • Ergebnis ist ein Objekt mit einem Array je Kategorie
  • intro_javascript/group-by.js
    const complex_data = [
      { name: "Remote Control", costs: 4, inventor: "heinz"  },
      { name: "Swinter", costs: 4, inventor: "ferb" },
      { name: "Audience Control", costs: 7, inventor: "heinz" },
      { name: "Cute-Puppy-Call", costs: 8, inventor: "heinz" },
    ]
    
    const groupBy = function(arr, keyFunc) {
      const toReturn = {};
      arr.forEach(v => {
        const key = keyFunc(v);
        const items = toReturn[key];
        if (items) {
          items.push(v);
        } else {
          toReturn[key] = [v];
        }
      });
    
      return (toReturn);
    }
    
    console.log(groupBy(complex_data, v => v.inventor));
    console.log(groupBy(complex_data, v => v.costs));
  • { heinz:
       [ { name: 'Remote Control', costs: 4, inventor: 'heinz' },
         { name: 'Audience Control', costs: 7, inventor: 'heinz' },
         { name: 'Cute-Puppy-Call', costs: 8, inventor: 'heinz' } ],
      ferb: [ { name: 'Swinter', costs: 4, inventor: 'ferb' } ] }
    { '4':
       [ { name: 'Remote Control', costs: 4, inventor: 'heinz' },
         { name: 'Swinter', costs: 4, inventor: 'ferb' } ],
      '7':
       [ { name: 'Audience Control', costs: 7, inventor: 'heinz' } ],
      '8': [ { name: 'Cute-Puppy-Call', costs: 8, inventor: 'heinz' } ] }
    

Fehlerbehandlung mit try und catch

Typische Syntax mit try- und catch-Blöcken
Im Rahmen dieser Vorlesung: Nur zum behandeln von Fehlern in der Standardbibliothek nötig
Ausnahmen müssen nicht selbstständig geworfen werden
  • intro_javascript/exceptions.js
    try {
      JSON.parse("{ foo: 'bar' }");
      console.log("Success!");
    } catch (ex) {
      console.info(ex);
    } finally {
      console.log("All Done!");
    }
    
  • SyntaxError: Unexpected token f in JSON at position 2
        at JSON.parse (<anonymous>)
        at [stdin]:2:8
        at Script.runInThisContext (vm.js:124:20)
        at Object.runInThisContext (vm.js:314:38)
        at Object.<anonymous> ([stdin]-wrapper:9:26)
        at Module._compile (internal/modules/cjs/loader.js:816:30)
        at evalScript (internal/process/execution.js:60:25)
        at readStdin (internal/main/eval_stdin.js:19:3)
        at Socket.process.stdin.on (internal/process/execution.js:172:5)
        at Socket.emit (events.js:198:15)
    All Done!
    

Besonderheiten von JavaScript

(Überspitzter) ursprünglicher Zweck von JavaScript: Eine Sprache für unnötige Animationen
Das hat sich geändert
Ursprüngliche Zielgruppe: Nicht-Programmierer
Ursprüngliches Ergebnis: Nicht-Programm (?)
Lesenswertes Interview mit dem “Erfinder” von JavaScript: Brendan Eich
Macht deutlich: Viele der Probleme resultieren aus der Annahme, dass ein Programm schon nicht länger als 100 Zeilen werden würde
Pragmatische Faustregel: Wenn JavaScript die Möglichkeit hat etwas unsinniges zu tun oder krachend abzubrechen …
… wird es etwas unsinniges tun.

Polyfills

Aufgrund der dynamischen Natur von JavaScript: Erweiterung der Standardbibliothek zur Laufzeit
Bereitstellung von neuen Features in alten Laufzeitumgebungen
Alle hier gezeigten Iterationsfunktionen lassen sich auch in älteren Browsern nutzen
Code wird dann nicht vom Browser, sondern vom Programm bereitgestellt
Im Zusammenhang mit Objektorientierter Programmierung: Thema der fortgeschrittenen Veranstaltung!

Automatic Semicolon Insertion

Automatisches einfügen von Semikoli, die man vergessen haben könnte
Beschrieben im ECMA Standard 262, Kapitel 11.9.1
Regel lautet (stark verkürzt): “Wenn eine unerwartete neue Zeile oder } gefunden wird, füge ein Semikolon ein. Wenn der Quelltext dann gültig ist, ist er bestimmt genau so gemeint.”
Tatsächlich sind es drei Regeln, mit relativ vielen komplizierten Details
  • undefined
    

null und undefined

Viele Sprachen haben einen “undefinierten” Wert, JavaScript hat zwei
  • Intern genutzt wird jedoch nur undefined
  • Der Wert null muss explizit vom Benutzer vergeben werden
  • null == undefined, aber null !== undefined
Einfachster Ausweg: Ignorieren Sie die Existenz des Wertes null und verwenden Sie grundsätzlich undefined als "fehlenden" Wert.

Strikter Modus

Schaltet gefährliche oder mehrdeutige Funktionen von JavaScript ab
  • Inkompatibel zu älteren Quelltexten
  • Lesetipp (nach dem Kapitel über fortgeschrittenes JavaScript): Dokumentation im MDN
Kann für Dateien oder für Funktionen aktiviert werden
"use strict"; als erste Zeile der Datei oder des Funktionsrumpfes
Schaltet leider nicht das automatische Einfügen von Semikoli ab
Einzige Abhilfe: Korrekten Code schreiben
Tipp für die Übung: Verwenden Sie "use strict"; als erste Anweisung in jeder Datei. Dadurch werden sehr wenige, aber dafür sehr gravierende, "leise" Fehler zu lauten Abstürzen.
  • intro_javascript/strict-off.js
    undefined = Math.PI;
    console.log(undefined);
    
    false.hello = "hello";
    console.log(false.hello);
    
    let myVar = 42;
    myVar.value = "zweiundvierzig"
    console.log(myVar.value);
  • intro_javascript/strict-on.js
    'use strict';
    
    undefined = Math.PI;
    console.log(undefined);
    
    false.hello = "hello";
    console.log(false.hello);
    
    let myVar = 42;
    myVar.value = "zweiundvierzig"
    console.log(myVar.value);
  • undefined
    undefined
    undefined
    
  • [stdin]:3
    undefined = Math.PI;
              ^
    
    TypeError: Cannot assign to read only property 'undefined' of object '#<Object>'
        at [stdin]:3:11

“Casting”-Abkürzungen

JavaScript sieht keine Syntax vor, um einen bestimmten Datentypen zu erzwingen
Fällt bei if Ausdrücken nicht auf, wohl aber bei JSON-Dokumenten
Abhilfe mit kruder Notation
  • Doppelte Negation mit !! erzwingt einen booleschen Wert
  • Unäres + vor einer Variable erzwingt einen Zahlenwert
  • intro_javascript/cast-boolean.js
    const complex_data = [
      { name: "Matt", born: "1978" },
      { name: "Chris", born: 1978 },
      { name: "Dominic", born: "1977" },
    ]
    
    function hasPerson(name) {
      return (complex_data.find(person => person.name === name));
    }
    
    console.log("Phineas? ", hasPerson("Phineas"));
    console.log("Matt?    ", hasPerson("Matt"));
    
    console.log("Phineas!!?", !!hasPerson("Phineas"));
    console.log("Matt!!?   ", !!hasPerson("Matt"));
    
    function getAge(person) {
      return (+(person.born));
    }
    
    console.log("Roher Wert:", JSON.stringify(complex_data[0].born));
    console.log("Roher Wert:", JSON.stringify(complex_data[1].born));
    
    console.log("Zahlwert:  ", JSON.stringify(getAge(complex_data[0])));
    console.log("Zahlwert:  ", JSON.stringify(getAge(complex_data[1])));
    
  • Phineas?  undefined
    Matt?     { name: 'Matt', born: '1978' }
    Phineas!!? false
    Matt!!?    true
    Roher Wert: "1978"
    Roher Wert: 1978
    Zahlwert:   1978
    Zahlwert:   1978
    

(Noch) kein einheitliches Modulsystem

Wie sollten komplizierte Programme ausgeliefert werden?
  • Historische Antwort: Es gibt keine komplizierten Programme in JavaScript
  • Aktuelle Antwort: Es gibt (mindestens) drei mögliche Modulsysteme dafür: AMD, CommonJS, ECMA Script 6
Ursprünglicher Ansatz: Skripte einfach in der korrekten Reihenfolge laden
  • Sehr fehleranfällig, Abhängigkeiten müssen manuell gepflegt werden
  • Komplizierter Umgang mit zyklischen Abhängigkeiten
  • Sehr einfache Umsetzung ohne Kompilierungsschritt
“Verbesserung”: Alle Skripte in einer Datei zusammenfassen
  • Erzeugt ohne weitere Maßnahmen häufig eine unnötig große Datei
  • Erzwingt einen Kompilierungsschritt
Aktuell: Modulsystem mit ECMAScript 6 standarisiert
Setzt sich hoffentlich möglichst schnell durch
Problematisch: Unterschiedliche Ladevorgänge für JavaScript im Browser oder auf dem Server
  • Server hat Zugriff auf das Dateisystem, kann dynamisch Dateien nachladen
  • Mehr und mehr historisch: Browser laden keinen Quelltext nach