Stand: 09.04.2019

Lernziele

Dieses Kapitel ist eine Einführung in die Programmiersprache JavaScript. Zielgruppe sind Studierende, die bereits eine objektorientierte Programmiersprache kennen, vorzugsweise Java.

Begleitend zu diesem Kapitel können Sie sich auch hier noch weiter informieren:

8.1 Hintergrund

JavaScript ist die dominante Programmiersprache in der Webentwicklung, besonders im Frontend-Bereich. Eine spannende Entwicklung ist, dass JavaScript auch immer mehr im Backend-Bereich benutzt wird (besonders durch das Node.js-Framework), so dass es sich für Webentwickler/innen doppelt lohnt, sich mit JavaScript zu beschäftigen.

In diesem Kapitel geht es um die Sprache an sich. Wie die Sprache dann in Webseiten verwendet werden kann, lernen wir im nächsten Kapitel.

8.1.1 JavaScript und ECMAScript

Man sollte wissen, dass JavaScript "nur" eine mögliche Implementierung einer ideellen Sprachdefinition namens ECMAScript ist. Es ist immer eine gute Idee, Konzept und Implementierung zu trennen. Nur so kann man verhindern, dass reine Implementationsaspekte die Weiterentwicklung treiben und nur so kann man zulassen, dass es mehrere unabhängige Implementierungen geben kann.

Warum muss man das wissen? Die Entwicklung von ECMAScript ist der Realisierung von JavaScript-Engines (s.u.) weit voraus. Derzeit liegt zum Beispiel ECMAScript in Version 9 vor (seit 2018), aber herkömmliche Browser unterstützen lediglich Version 5 zu 100%. Das heißt, wenn man wissen will, welche Sprach-Features ein Browser unterstützt, muss man schauen welche ECMAScript-Version unterstützt wird. Wenn man wissen will, in welche Richtung sich JavaScript entwickeln wird, kann man sich die höheren Versionen bereits jetzt ansehen.

Derzeit sprechen viele von ECMAScript 6 oder kurz ES6, wenn es um die Frage gibt, welche "Geschmacksrichtung" von JavaScript man lernen/anwenden sollte (siehe auch State of JavaScript 2018 - JavaScript Flavors).

Einige relevante Artikel zum Thema:

8.1.2 JavaScript-Engines

Bei Programmiersprachen unterscheidet man zwischen Sprachen, die von einem Compiler übersetzt werden (z.B. C++), und Sprachen, die von einem Interpreter übersetzten werden (z.B. JavaScript, Python, PHP). Der Unterschied besteht darin, dass ein Compiler den Code einmalig in Maschinensprache übersetzt und ab dann immer nur der Maschinencode ausgeführt wird. Ein Interpreter hingegen übersetzt während das Programm läuft immer jeweils die aktuelle Code-Zeile und führt diese aus. Beide Ansätze haben Vor- und Nachteile.

Fun Fact: Bei Java wird eine Mischung verwendet; der Java-Code wird zunächst in Bytecode (nur "fast" Maschinencode) compiliert und dieser Bytecode wird dann für jeden Programmlauf von einem Interpreter, der Java Runtime Engine (JRE), ausgeführt.

Die JavaScript-Engine ist die Software, die die JavaScript übersetzt und/oder ausführt. JavaScript ist ursprünglich eine interpretierte Sprache, aber moderne Engines verfolgen den Compiler-Ansatz, um noch mehr Performance rauszuholen.

Da JavaScript in der Regel im Browser ausgeführt wird, hat jeder Browser eine eigene Engine, z.B.

V8 ist die derzeit am weitesten verbreitete Engine (wird z.B. auch von Node.js und im Opera-Browser verwendet). Die Engine stammt von Google und ist open source. Tatsächlich wird hier der Code nicht interpretiert, sondern compiliert, um eine höhere Performance zu erreichen.

Vorsicht Verwechselungsgefahr: Im Gegensatz zur JavaScript-Engine ist die Browser-Engine dafür verantwortlich, HTML/CSS umzusetzen (Layout und Rendering). Man muss etwas aufpassen, dass man diese beiden nicht verwechselt. Bekannte Browser-Engines sind Gecko (Firefox), WebKit (Safari) und Blink (Chrome).

8.1.3 Literatur

Folgende Bücher sind interessant:

Englischsprachige Bücher:

8.2 Code schreiben und ausführen

Wir stellen hier zwei Möglichkeiten vor, um in JavaScript-Code zu schreiben und den Code auszuführen:

  1. Mit einem Texteditor (z.B. Atom) Code schreiben und in einem Browser (z.B. Chrome oder Firefox) ausführen
  2. Beides in einer Programmierumgebung (IDE: Integrated Development Environement) namens WebStorm erledigen

Beides hat Vor- und Nachteile. Im Hinterkopf sollte man behalten, dass (fast) jeder JavaScript-Code letztlich in einem Browser läuft und dort getestet werden muss.

8.2.1 JavaScript mit Editor und Browser

Jeder Browser "versteht" JavaScript. Das können wir ausnutzen, indem wir eine einfache HTML-Seite erstellen und dort unseren JavaScript-Code einbetten.

Den Code testen wir, indem wir die Seite in einem Browser öffnen und im Browser eine Konsole öffnen, um Ausgaben und Fehlermeldungen zu sehen. Um die HTML-Seite zu erstellen, verwenden wir wie gewohnt einen Editor wie Atom.

Einbetten

Um JavaScript in eine Webseite - also eine HTML-Datei - einzubauen, verwenden Sie das <script>-Element. In dieses Element fügen Sie JavaScript-Code hinein, z.B. eine einfache Konsolenausgabe (die Konsolenausgabe erklären wir im Abschnitt "Erste Schritte"):

<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8">
    <title>JavaScript</title>
  </head>
  <body>
    <p>JavaScript running...</p>
    <script>
      console.log("Hello, world");
    </script>
  </body>
</html>

Konsole

Aber wo sehen Sie die Ausgabe? Laden Sie die HTML-Datei in ihrem Browser (sollte mit Drag'n Drop gehen) und öffnen Sie die JavaScript-Konsole. Diese finden Sie

(Diese Begriffe ändern sich von Zeit zu Zeit, also evtl. nach ähnlichen Bezeichnungen suchen.)

Hier sehen Sie die HTML-Datei ohne Konsole in Chrome:

Chrome

Wenn Sie die Konsole öffnen, erscheint sie rechts:

Chrome mit Konsole

In FireFox erscheint sie (derzeit) unten:

FireFox mit Konsole

Der Nachteil dieser Vorgehensweise ist offensichtlich, dass Sie mit zwei Tools arbeiten (Editor und Browser). Wenn Sie den Code ändern, reicht im Browser ein Reload, um die Änderung zu sehen (hier lohnt es sich, den Tastaturkürzel für Reload zu verinnerlichen).

Der Vorteil ist, dass Sie im Browser genau dort testen, wo Ihr Code später auch zum Einsatz kommt.

8.2.2 JavaScript mit der Entwicklungsumgebung WebStorm

WebStorm ist eine integrierte Entwicklungsumgebung der Firma JetBrains, die auch IntelliJ IDEA für Java entwickelt haben. Diese Umgebung ist kostenpflichtig, aber für Studierende und Lehrende frei nutzbar.

Der Vorteil gegenüber der Methode "Editor + Browser" ist, dass Sie nicht ständig zwischen Editor und Browser wechseln müssen. In WebStorm haben Sie ein Code-Fenster und ein Konsolen-Fenster. Wenn Sie auf "Run" drücken, sehen Sie die Ausgaben im Konsolen-Fenster.

Der Nachteil ist, dass WebStorm, sobald Sie es professionell einsetzen, kostenpflichtig wird und dass Sie letztlich doch immer nochmal im Browser testen müssen.

Am Rande sei bemerkt, dass WebStrom weit mehr kann, als nur JavaScript-Code zu testen, und für die Entwicklung komplexer Webprojekte verwendet wird.

Oberfläche

In der Abbildung unten sehen Sie das Code-Fenster (rechts-oben) und die Konsole (unten). Links oben sehen Sie den Projektbereich mit Verzeichnisstruktur und Dateien.

WebStorm

Lizenz und Download

Auf der Webseite von WebStorm finden Sie unter dem Punkt For students and teachers die Möglichkeit, eine freie Lizenz als Student*in zu bekommen.

Erster Test

Für einen ersten Test tun Sie folgendes:

  1. Erstellen Sie ein neues Projekt unter File > New > Project..; wählen Sie "Empty Project".
  2. Erstellen Sie eine neue Datei unter File > New > JavaScript File.
  3. Führen Sie den Code in dieser Datei aus mit Run > Run...

Haben Sie eine Datei einmal ausgeführt, können Sie mit dem grünen Play-Button (rechts-oben in der Toolbar) die Datei wiederholt ausführen.

8.2.3 Erste Schritte

Konsole und Konsolenausgabe

Eine Konsole ist eine Ausgabebereich für Textmeldungen. Solche Ausgaben werden im Grunde nur während der Programmentwicklung genutzt, um Infos zum laufenden Programm auszugeben. In JavaScript verwenden Sie console.log():

console.log('hello world');

Sie können in JavaScript meist zwischen doppelten und einfachen Anführungszeichen wählen. Es funktioniert also auch:

console.log("hello world");

Eine interessante Variante ist die Gruppierung von Konsolenausgaben, wenn Sie z.B. viele oder sehr lange Ausgaben in Ihrem Programm haben. Alle Ausgaben dieser Gruppierung werden dann zunächst eingeklappt dargestellt und können bei Bedarf aufgeklappt werden.

Sie eröffnen eine neue Gruppe mit einer Bezeichnung Ihrer Wahl mit

console.group('Schleifentest');

eine Gruppe eröffnen. Dann schreiben Sie normal auf die Konsole

console.log('1');
console.log('2');
console.log('3');

dann schließen Sie die Gruppe mit

console.groupEnd();

Kommentare

Kommentare funktionieren wie in Java.

Es gibt den Zeilenkommentar, der alles ab // ignoriert:

// Das ist ein Kommentar ..
console.log('hello world'); // .. das auch

Und es gibt den Blockkommentar, der mehrere Zeilen umfassen kann. Er beginnt mit /* und endet mit */:

/*
   Das ist ein Kommentar,
   der über mehrere Zeilen
   geht.
*/
console.log('hello world');

8.3 Datenstrukturen

8.3.1 Variablen und Typen

Variablen in JavaScript funktionieren prinzipiell ähnlich wie in Java, aber der Umgang mit Typen ist wesentlich lockerer, d.h. Sie können in der Regel die Angabe von Typen weglassen.

8.3.2 Globale Variablen

Wenn Sie eine Variable "foo" erstellen wollen, schreiben Sie folgende Deklaration mit dem Schlüsselwort var:

var foo;

Sie erzeugen so eine neue globale Variable.

Solange Sie keinen eigenen Wert zugewiesen haben, enthält die Variable einen speziellen Wert, den JavaScript als undefined bezeichnet. Da wir hier keinen Typen angegeben haben, kann JavaScript nicht einfach einen Wert wie 0 oder false zuweisen, da diese Werte ja vom Typ abhängen.

Sie können bei der Deklaration auch gleich einen Wert zuweisen (das nennt man auch Initialisierung):

var foo = 10;

Das Schlüsselwort var können Sie auch weglassen:

foo = 10;

Lokale Variablen und Konstanten (ES6)

Seit ECMAScript 6 gibt es statt var auch let:

let foo;

Der Unterschied ist, dass mit var eine Variable global angelegt wird, wohingegen das let eine lokale Variable erstellt, wie es in der Regel erwünscht ist.

Mit const kann man eine Konstante deklarieren (ähnlich wie mit dem Schlüsselwort "final" in Java), d.h. die Variable darf im weiteren Programmverlauf nicht verändert werden:

const pi = 3.14159;

Implizite Deklaration

Wie man oben sieht, müssen Sie sich nicht entscheiden, ob z.B. eine Zahl oder ein String hineinkommt. Bei der ersten Zuweisung entscheidet JavaScript einfach aufgrund des Werts, welchen Typ es nimmt. Man spricht auch von impliziter Deklaration.

foo = 10;

Weitere Beispiele für Variablen:

x = 100;
y = 80;
distance = 43.8;
isActive = false;
name = "Sally";
name2 = 'Harry'; // String mit einfachen Anführungszeichen

Beachten Sie, dass Sie bei Strings sowohl die einfachen als auch die doppelten Anführungszeichen verwenden können.

Dynamische Typisierung

In JavaScript können Sie sich jederzeit umentscheiden und den Typ einer Variablen ändern. Man sagt daher auch, JavaScript ist dynamisch typisiert

foo = 10; // hier soll eine Zahl rein
foo = "Harry"; // äh... doch nicht

Sie können den Typ einer Variablen beliebig oft ändern. Dynamische Typisierung kommt auch bei Arrays zum Tragen, wo sie besonders nützlich ist.

Typen

JavaScript hat dennoch Typen und zwar die folgenden:

Sie finden den Typ mit typeof:

a = 25;
typeof(a);

ergibt

"number"

Oder direkt mit einem Wert

typeof("hallo");

ergibt

"string"

Bei Zahlen (number) verwendet JavaScript immer Gleitkommazahlen mit doppelter Präzision (8 Byte), entspricht also dem Datentyp double in Java.

8.3.3 Strings

Strings funktionieren ähnlich wie in Java. Man kann Strings mit einfachen oder doppelten Anführungszeichen kennzeichnen und Variablen zuweisen. Wie in Java können mehrere Strings mit dem Operator + zusammengefügt werden (String-Konkatenation).

s1 = "hallo";
s2 = 'welt';
s3 = s1 + " " + s2;
console.log(s3); // hallo welt

Strings sind, wie in Java, Objekte. Die Länge des Strings steht in der Eigenschaft length:

s = "Peter";
console.log(s.length); // 5

Wie in Java stehen Ihnen für wichtige Aufgaben wie das Suchen, Teilen und Zusammenfügen von Strings Methoden zur Verfügung, die Sie per Punktnotation aufrufen.

Groß-/Kleinschreibung

Groß-/Kleinschreibung ändern Sie mit den Methoden toLowerCase() und toUpperCase():

str = "Hallo Welt";
console.log(str.toLowerCase()); // hallo welt
console.log(str.toUpperCase()); // HALLO WELT

Beachten Sie, dass diese Methoden neue Strings zurückgeben und nicht etwa den bestehen String ändern.

Suchen

Die Methode indexOf() gibt wie in Java die Stelle (Zahl) zurück, an der der Suchstring gefunden wurde. Die Methode includes() entspricht der Java-Methode contains(), gibt also einen booleschen Wert zurück, der anzeigt, ob der Teilstring überhaupt enthalten ist.

text = "I expect you to die, Mister Bond.";
console.log(text.indexOf("you")); // 9
console.log(text.includes("Bond")); // true

Schneiden

Wie in Java gibt es die Methode substring(), um einen Teilstring herauszuschneiden. Die Methode gibt diesen Teilstring als neuen String zurück. Wie in Java, wird das Original nicht verändert.

Die Methode gibt es mit einem oder zwei Parametern. Der erste Parameter gibt die Startstelle an (inklusive) und der zweite die Endstelle (exklusive).

str = 'james bond';
console.log(str.substring(0, 5)); // james

Wird der zweite Parameter weggelassen, wird bis zum Ende geschnitten.

console.log(str.substring(6)); // bond

Trimmen

Die Methode trim() entfernt alle führenden und anhängigen Leerzeichen.

str = "Haustür";
console.log(str.substring(0, 4)); // Haus
console.log(str.substring(4)); // tür

input = "Name: James Bond   ";
name = input.substring(5).trim();
console.log(name); // "James Bond"

Auch hier wird ein neuer String zurückgegeben.

Zerlegen

Die Methode split() erlaubt es, einen String anhand eines frei definierbaren Trennzeichen in mehrere Teile zu zerlegen. Die Teile liegen dann als Array vor.

input = "Peter,Paul,Mary";
parts = input.split(",");
console.log(parts); // ["Peter", "Paul", "Mary"]

Im CSV-Dateiformat werden Daten z.B. mit Komma oder Semicolon getrennt und können mit Hilfe von split verarbeitet werden.

8.3.4 Arrays

Zum Nachschlagen beim Thema Arrays schauen Sie in die JavaScript Array Reference.

Vergleicht man Arrays in JavaScript mit Arrays in Java, so sind die Ähnlichkeiten eher oberflächlich (Notation mit eckigen Klammern). Ansonsten funktionieren JavaScript-Arrays eher wie Listen, denn im Gegensatz zu Java-Arrays kann ein JavaScript-Array:

Einen neuen, leeren Array erstellen Sie mit:

a = [];

Sie können einen Array direkt mit Werten initialisieren. Diese Werte können unterschiedlichen Typs sein:

b = ["Joe", "Moe", "Zoe"]; // Array mit Strings
c = [1, "zwei", 3.5]; // Werte unterschiedlichen Typs

Der Zugriff auf die Inhalte sieht genauso aus wie in Java:

console.log(b[0]); // gibt "Joe" aus

Und natürlich können Sie die Werte auch durch Zuweisung verändern:

c[0] = b[2]; // setzt erstes Element von c auf "Zoe"

Neue Werte fügen Sie mit push() hinzu. Die Werte werden dem Array ans Ende angefügt. Sie verlängern den Array also um eins.

b.push("Cloe"); // ["Joe", "Moe", "Zoe", "Cloe"]
c.push(4); // [1, "zwei", 3.5, 4]

Die Operation pop() gibt das letzte Element zurück und entfernt dieses, verkürzt also den Array um eins. Zusammen realisieren push/pop die Funktionsweise eines Stacks oder Stapels.

a = [1, 3, 5];
el = a.pop(); // bekommt 5, a wird zu [1, 3]

Zwei ähnliche Methoden sind shift() und unshift(). Sie betreffen das erste Element, nicht das letzte.

Arrays sind wie in Java Objekte und haben wie in Java sowohl Methoden (Funktionen) sowie Eigenschaften. Eigenschaften sind Variablen, die zum Objekt gehören und auf die man per Punktnotation zugreifen kann.

Eigenschaften und Methoden

Ein Array ist wie in Java ein Objekt. Somit hat ein Array Eigenschaften und Methoden, die über Punktnotation aufgerufen gewerden.

Die Länge eines Arrays erhalten Sie mit der Eigenschaft length:

console.log(b.length); // 4

Die Methode sort sortiert die Elemente des Arrays. Beachten Sie, dass das Array dabei verändert wird:

a = [200, 300, 100];
a.sort();
console.log(a); // [ 100, 200, 300 ]

Die Sortierung ist nicht immer intuitiv:

a = [ "Sally", "Albert", "Camus", "z", "a"];
a.sort();
console.log(a); // [ 'Albert', 'Camus', 'Sally', 'a', 'z' ]

Die Methode join baut alle Arrayelemente zu einem String zusammen. Man kann der Methode einen Trenn-String übergeben, der zwischen die Elemente gefügt wird.

a = [ "Sally", "Albert", "Camus"];
console.log(a.join("_")); // Sally_Albert_Camus

Die Methode slice gibt einen Teilarray zurück. Dazu übergibt man zwei Parameter: den Index den ersten Elements und den Index des letzten Elements (dies ist exklusive, d.h. es ist nicht mehr enthalten):

a = [ "Sally", "Albert", "Camus", "Ruth", "Herbert"];
console.log(a.slice(1,3)); // [ 'Albert', 'Camus' ]

8.3.5 Assoziative Arrays

Bei Arrays speichern wir eine Reihe von Elementen, auf die wir mit Hilfe von Indexzahlen zugreifen können. Manchmal wollen wir aber nicht über Zahlen auf diese Elemente zugreifen, sondern z.B. über Wörter. Bei einem Telefonbuch zum Beispiel wollen wir zu einem Namen eine Nummer speichern. So etwas nennt man einen assoziativen Speicher. Andere Namen sind: Dictionary, Hashtabelle oder assoziativer Array (siehe auch Wikipedia).

In JavaScript können wir so ein Telefonbuch speichern. Sie sehen drei Paare, jedes Paar besteht aus einem Schlüssel (z.B. "Harry") und dem dazugehörigen Wert (bei "Harry" wäre das 123):

phonebook = { "Harry": 123, "Sally": 456, "Betty": 789 };

Sie können den ganzen Array ausgeben mit

console.log(phonebook);
{ Harry: 123, Sally: 456, Betty: 789 }

Der Zugriff erfolgt ähnlich wie bei Arrays über die eckigen Klammern: statt der Indexzahl gibt man jetzt den Schlüssel an und erhält den Wert zurück:

console.log(phonebook["Sally"]); // 456

Neue Werte fügt man durch einfache Zuweisung hinzu:

phonebook['Lonny'] = 555;
console.log(phonebook);
{ Harry: 123, Sally: 456, Betty: 789, Lonny: 555 }

Unerwünschte Einträge können mit delete gelöscht werden.

delete phonebook['Harry'];

Die Werte müssen natürlich nicht Zahlen sein, sondern können auch boolesch sein:

vegetarian = { "Harry": true, "Sally": true, "Betty": false };

console.log(vegetarian["Betty"]); // false

Oder auch Strings:

favorite_color = { "Harry": "red", "Sally": "green", "Betty": "blue" };

console.log(favorite_color["Harry"]); // red

Sie können wie bei Arrays beliebige Typen mischen:

data = { "important": true, "name": "Bond", "id": 7 };

Und Sie können sich den ganzen Array ausgeben lassen:

console.log(data);

8.3.6 Mengen

Es gibt Fälle, da möchte man nicht zwei "gleiche" Elemente in einem Array haben. Oder anders gesagt: man möchte nur Unikate im Array speichern. Dafür gibt es seit ES6 in JavaScript das Konzept der Menge oder - auf Englisch - einem Set.

Sie erstellen einen Set so:

let s = new Set();

So fügen Sie Elemente hinzu:

s.add('foo');
s.add('bar');
s.add('foo');
s.add('shoe');
s.add('foo');

Der Set hat nach diesen Anweisungen nur drei Elemente, da "gleiche" Element nicht hinzugefügt werden:

Set { 'foo', 'bar', 'shoe' }

Im Gegensatz zu Arrays können Sie im Set gezielt Elemente löschen:

s.delete('foo');

Sie können einen Set mit forEach durchlaufen (s.u.):

s.forEach(x => console.log('Element: ' + x));
Element: bar
Element: shoe

8.4 Kontrollfluss

8.4.1 If, Switch, ternärer Operator

If

Bedingte Anweisungen mit if und else sehen exakt so aus wie in Java.

p = 52;

if (p > 50) {
  console.log("Prüfung bestanden");
} else {
  console.log("Leider durchgefallen");
}

Auch das else if sollte vertraut sein:

p = 92;

if (p > 90) {
  console.log("Prüfung exzellent bestanden");
} else if (p > 50) {
  console.log("Prüfung bestanden");
} else {
  console.log("Leider durchgefallen");
}

Switch

Und selbst die Fallunterscheidung mit switch funktioniert wie in Java:

wahl = 10;

switch (wahl) {
  case 0: console.log("Sie kaufen den Koffer");
  break;
  case 1: console.log("Sie kaufen den Toaster");
  break;
  default:
  console.log("Sie kaufen nichts");
}

Ternärer Operator

Wie in Java gibt es auch in JavaScript den ternären Operator. Dieser ermöglicht die Auswahl zwischen zwei alternativen Werten aufgrund eines booleschen Ausdrucks. Haben wir z.B. die Variable:

let have_time = true;

können wir aufgrund des booleschen Werts zwischen zwei Strings entscheiden:

console.log("Heute passt es " + (have_time ? "sehr gut" : "leider nicht"));

Der ternäre Operator ist in solchen Fällen deutlich schlanker als ein If.

8.4.2 Bedingungen

Wie in Java...

Für If, Switch und Schleifen müssen Sie Bedingungen formulieren. Diese Bedingungen werden genauso wie in Java formuliert. Es gibt die beiden Literale

true
false

Sie können numerische Vergleiche durchführen

x >= 50

und Sie können die logischen Operatoren UND && (Und), ODER || und NICHT ! verwenden, z.B.

x > 50 && x < 100

Wie in Java müssen Sie die Präzedenz beachten (NICHT vor UND vor ODER) und können Klammern einsetzen, um die Ausführungsreihenfolge eines logischen Ausdrucks zu steuern.

Bei Gleichheit gibt es in JavaScript das doppelte Gleichheitszeichen == und das dreifache Gleichheitszeichen ===. Bei dem doppelten Gleichheitszeichen wird der Datentyp ignoriert, d.h. die folgenden Bedingungen sind wahr

5 == "5"
5 == 5

Nicht wie in Java...

Bei dem dreifachen Gleichheitszeichen muss zusätzlich der Typ übereinstimmen, d.h. diese Bedingung ist nicht wahr:

5 === "5" // false

Beachten Sie, dass JavaScript nicht zwischen ganzen Zahlen und Gleitkommazahlen unterscheidet, so dass beide folgenden Bedingungen wahr sind:

5 == 5.0
5 === 5.0

Im Gegensatz zu Java, kann man in JavaScript auch nicht-boolesche Werte als Bedingung verwenden. Dabei gilt:

Sie können das ausprobieren mit

if (0) {
  console.log('true');
} else {
  console.log('false');
}

x = 5;
if (x) {
  console.log('true');
} else {
  console.log('false');
}

if ('foo') {
  console.log('true');
} else {
  console.log('false');
}

if ('') {
  console.log('true');
} else {
  console.log('false');
}

if (undefined) {
  console.log('true');
} else {
  console.log('false');
}

8.4.3 Schleifen

Eine For-Schleife sieht so aus wie in Java, nur dass bei der Laufvariablen kein Datentyp steht:

for (i = 0; i < 3; i++) {
  console.log("hello"); // wird 3x ausgeführt
}

Noch sauberer ist es, wenn man bei der Laufvariablen ein let voranstellt, um klar zu stellen, dass i eine lokale Variable ist:

for (let i = 0; i < 3; i++) {
  console.log("hello"); // wird 3x ausgeführt
}

Im ersten Fall können Sie hinter der Schleife noch auf i zugreifen, im zweiten Fall nicht.

8.4.4 Schleifen und Arrays

Auch in JavaScript sind Arrays und Schleifen perfekte Partner, da wir sehr elegant mit einer Laufvariablen von 0 bis zur Länge des Arrays minus eins laufen können:

b = ["Joe", "Moe", "Zoe"];

for (i = 0; i < b.length; i++) {
  console.log(b[i]);
}

Assoziative Arrays mit For-in

Bei assoziativen Arrays haben wir Schlüssel und Werte.

data = { "important": true, "name": "Bond", "id": 7 };

In JavaScript benutzen wir daher die for-in-Schleife. Diese Schleife durchläuft die Schlüssel:

for (k in data) {
    console.log(k);
}

Wir können natürlich den Schlüssel nutzen, um den jeweiligen Wert zu beziehen:

for (k in data) {
    console.log("Schlüssel " + k + " hat Wert " + data[k]);
}

8.5 Funktionen

Eines der "Killer-Features" von JavaScript ist die Möglichkeit der funktionalen Programmierung. Damit ist u.a. gemeint, dass Funktionen in Variablen gespeichert und durch Parameterübergabe "herumgereicht" werden können. Man kann also in vielen Situationen kleine (oder große) Stückchen Code weiterreichen oder anhängen. Wie nützlich sowas ist, haben Sie vielleicht bei der GUI-Programmierung in JavaFX gesehen (Stichwort "Lambda-Ausdrücke"). Technisch funktioniert das so, dass Funktionen in JavaScript als Objekte gespeichert werden.

Aber fangen wir vorn an.

8.5.1 Deklaration, Parameter, Rückgabe

Funktionen werden im einfachsten Fall global deklariert (ähnlich wie in Processing) und sind anschließend überall aufrufbar. Dies funktioniert über das Schlüsselwort function.

Eine einfache Funktion sowie ihr Aufruf:

// Deklaration
function printOne() {
  console.log(1);
}

// Aufruf
printOne(); // Ausgabe: 1

Eine Funktion mit einem Parameter. Auch bei Parametern wird - wie bei Variablen - kein Datentyp angegeben:

function sayHello(name) {
  console.log("Hello, " + name);
}

sayHello("Peter"); // Hello, Peter

Mehrere Parameter sind natürlich möglich und werden durch Komma getrennt.

Eine Funktion kann auch einen Rückgabewert haben. Wie in Java verwenden wir dazu das Schlüsselwort return. Im Gegesatz zu Java wird aber kein Rückgabetyp angegeben:

function sum(a, b, c) {
  return a + b + c;
}

console.log(sum(5, 10, 100)); // 115

Optionale Parameter

Wird eine Funktion mit weniger Parametern aufgerufen als in der Definition angegeben, so werden die Parameter, die nicht abgedeckt sind, mit "undefined" belegt. (Wird eine Funktion mit mehr Parametern als definiert aufgerufen, werden die überzähligen Parameter einfach ignoriert.)

Sie können das nutzen, um optionale Parameter zu definieren, wie hier Parameter "b":

function onetwo(a, b) {
    console.log("one: " + a);
    if (b != undefined) {
        console.log("two: " + b);
    }
}

Jetzt können Sie gefahrlos folgende Aufrufe programmieren:

onetwo("Harry");
onetwo("Harry", "Sally");

Variable Parameterzahl

Innerhalb einer Funktion gibt es automatisch eine Variable arguments, die alle übergebenen Paramter enthält, auch wenn es "zuviele" Parameter beim Aufruf gab. Dieses Variable ist ein Array, dass alle übergebenen Werte enthält. Sie können dieses Array nutzen, um Funktionen mit einer variablen Parameterzahl zu definieren:

function summe() {
    let s = 0;
    for (let i = 0; i < arguments.length; i++) {
        s += arguments[i];
    }
    return s;
}

Diese Funktion können Sie mit beliebig vielen Werten aufrufen. So sind etwa folgende Aufrufe möglich:

console.log(summe()); // 0
console.log(summe(42, -10)); // 32
console.log(summe(1, 2, 3)); // 6

Alternativ würde man natürlich auch mit einem Array als expliziten Parameter arbeiten.

8.5.2 Anonyme Funktionen

Eine weitere Möglichkeit, Funktionen zu erschaffen, ist, zunächst eine anonyme Funktion zu definieren (anonym heißt: die Funktion hat keinen Namen), und diese Funktion dann an eine Variable zu heften.

Hier eine "normale" Funktion mit Schlüsselwort function und dem Namen der Funktion (printOne):

function printOne() {
  console.log(1);
}

Jetzt definieren wir eine anonyme Funktion. Hier lassen wir den Namen einfach weg. Dafür weisen wir die resultierende anonyme Funktion (ein Objekt) der Variablen "printOne" zu:

var printOne = function () {
  console.log(1);
}

Der Aufruf ist in beiden Fällen:

printOne();

Ebenso können Sie Funktionen mit Parametern und Rückgabewert verfahren.

Callbacks

Warum soll das interessant sein? Ganz einfach: Sie können jetzt Funktionen "herumreichen", genauso wie Variablenwerte. Das wird insbesondere für Callbacks verwendet, wenn z.B. nach Ausführung einer Animationsfunktion foo eine andere Funktion bar aufgerufen werden soll, dann übergibt man der Funktion foo die Funktion bar als weiteren Parameter:

foo(1000, bar);

Oder man definiert die Callback-Funktion direkt hinein:

foo(1000, function () { console.log("finished"); });

Wir können uns das am Beispiel der Funktion setTimeout ansehen. Die Funktion bekommt zwei Parameter: (1) eine Funktion, die nach einer gewissen Dauer D ausgeführt werden soll und (2) die Dauer D in Millisekunden.

Wollen Sie einfach den String "one" nach zwei Sekunden ausgeben:

setTimeout(function () {
  console.log('one');
}, 2000);

Sie können sich den Timeout-Effekt z.B. mit diesem Code ansehen:

setTimeout(function () {
    console.log('one');
}, 2000);

setTimeout(function () {
    console.log('two');
}, 1000);

Sie sollten sehen:

two
one

Funktionen tauschen

Ein weiteres Beispiel: Sie können die Funktion printOne von oben auch an eine andere Variable binden:

var foo = printOne();

Und dann aufrufen:

foo();

Sie so im Verlauf Ihres Programms die "Bedeutung" einer Funktion ändern:

var halloW = function (name) {
  console.log("Hallo, Frau " + name);
}

var halloM = function (name) {
  console.log("Hallo, Herr " + name);
}

gruss = halloM;
gruss("Max Meier");

gruss = halloW;
gruss("Maria Müller");

Funktionen werden also als "ein Stück Funktionalität" gesehen. Diese Stücke können beliebig vertauscht und auch als Parameter übergeben werden.

Kurzschreibweise als Arrow Function

Ab ECMAScript 6 können Sie eine neue, sehr kompakte Schreibweise verwenden.

Statt

myPrint = function (x) {
  console.log(x);
}

schreiben Sie

myPrint = (x) => console.log(x);

oder (besonders wenn Sie mehr Code haben):

myPrint = (x) => {console.log(x);}

Mehrere Parameter werden durch Komma getrennt. Gibt es keine Parameter, schreibt man leere Klammern.

Unser Beispiel oben mit dem Timeout:

setTimeout(function () {
    console.log('one');
}, 2000);

sieht in der Kompaktschreibweise so aus:

setTimeout(() => console.log('one'), 2000);

Beispiel sort()

Die Funktion sort erlaubt es, einen Array zu sortieren:

a = [ 3, 1, 2 ];
a.sort();
console.log(a);
[ 1, 2, 3 ]

Wenn Sie eine andere Form der Sortierung haben möchten, können Sie sort auch eine Funktion mitgeben. Diese Funktion vergleicht zwei Elemente a und b und gibt (1) eine negative Zahl zurück, wenn a kleiner b ist, (2) Null zurück, wenn beide gleich sind oder (2) eine positive Zahl zurück, wenn a größer b ist.

Die "eingebaute" Vergleichsfunktion (für Zahlen) sieht so aus:

function compare(a, b) {
    return a - b;
}

Jetzt können Sie auch eine eigene Funktion definieren, die die Reihenfolge umdrehen soll:

function mycompare(a, b) {
    return b - a;
}

Um die Funktion bei sort anzuwenden, übergeben Sie sie als Parameter:

a = [ 3, 1, 2 ];
a.sort(mycompare);
console.log(a);
[ 3, 2, 1 ]

Alternativ können Sie die Funktion direkt bei Aufruf von sort anonym definieren:

a.sort(function(a, b) { return b - a; });

Oder in der Kurzschreibweise:

a.sort((a, b) => {return b - a; } );

8.5.3 forEach

Für Arrays gibt es in JavaScript die Methode forEach. Diese Methode wird auf dem Array aufgerufen:

a = ['Peter', 'Paul', 'Mary'];
a.forEach(...); // hier fehlt noch Code

Die Methode bekommt eine Funktion übergeben. Diese Funktion sollte einen Parameter haben. Diese Funktion wird dann für jedes Element im Array aufrufen, wobei das jeweilige Element als Parameter übergeben wird.

In nächsten Beispiel definieren wir die Funktion sayHello mit Parameter x vorab und übergeben sie dem forEach. Die Funktion wird dann für jedes der drei Elemente aufgerufen, wobei das x jeweils das aktuelle Element repräsentiert.

a = ['Peter', 'Paul', 'Mary'];

var sayHello = function (x) {
  console.log("Hello, " + x);
};

a.forEach(sayHello);

Noch üblicher ist es, dem forEach eine anonyme Funktion zu übergeben, denn eigentlich benötigen wir den Namen der Funktion nicht; die Funktion wurde ja ausschließlich für das forEach geschaffen.

Der Code wird so sehr kompakt:

a.forEach(function (name) {
  console.log("Hello, " + name);
});

Wenn wir die Kompaktschreibweise verwenden:

a.forEach((n) => console.log("Hello, " + n));

Hier ein Beispiel, wo wir einen zweiten Array befüllen:

a = ['Peter', 'Paul', 'Mary'];
b = [];

a.forEach((x) => b.push('Agent ' + x));

console.log(b);
[ 'Agent Peter', 'Agent Paul', 'Agent Mary' ]

Eine weitere typische Aufgabe ist die Summierung von Zahlen:

a = [ 10, 50, 40 ];
s = 0;
a.forEach((x) => s+=x);
console.log(s);
100

8.6 Objekte

In JavaScript gibt es Objekte, aber keine Klassen. Jeder, der Java kennt, müsste jetzt ins Grübeln kommen. In Java definiert man doch erst eine Klasse als eine Art Bauplan für Objekte. Wenn ich Autos speichern will, definiere ich eine Klasse Auto mit den Eigenschaften firma (String), modell (String) und kilometerstand (int) zum Beispiel. Erst dann kann ich Objekte (Instanzen) durch Instanziierung - Schlüsselwort new - erzeugen. In JavaScript gibt es keine Klassen, also auch keine Baupläne. Jedes Objekt wird individuell zusammengestellt.

8.6.1 Objekte und Eigenschaften

Ein Objekt in JavaScript ist einfach nur eine Ansammlung von Eigenschaften, die wir einer Variablen zuweisen:

let a1 = {
  firma: "VW",
  modell: "Golf",
  kilometerstand: 11032
}

In der Tat handelt es sich um einen assoziativen Array. Die Eigenschaften sind die Schlüssel, die jeweils einen Wert haben.

Da Sie hier keine Klasse als "Bauplan" haben, hindert Sie niemand daran, beim nächsten Objekt andere Eigenschaften zu wählen oder die Eigenschaften anders zu benennen.

let a2 = {
  typ: "Audi",
  variante: "A4",
  kilometer: 0
}

Diese Freiheit birgt natürlich das Risiko, dass Sie unabsichtlich eine Eigenschaft "falsch" nennen.

null

In JavaScript gibt es wie in Java den Bezeichner null, um anzuzeigen, dass eine Variable "eigentlich" einen Wert haben sollte, aber eben gerade keinen hat.

Sie können null zum Beispiel als Rückgabeoption verwenden, wenn eine Suchfunktion nicht findet.

Sie verwenden null ähnlich wie in Java als Wert:

let meinobjekt = null;

null wird nicht nur in Zusammenhang mit Objekten verwendet, ist aber hier besonders relevant.

Punktnotation

Auf die Eigenschaften des Objekts a1 können Sie mit Punktnotation zugreifen:

console.log(a1.firma);
console.log(a1.modell);
console.log(a1.kilometerstand);

Objekt mit Eigenschaften befüllen

Sie können über Punktnotation auch neue Eigenschaften hinzufügen:

a1.farbe = "rot";

Jetzt können Sie ein Objekt auch zunächst als "leeres Objekt" erzeugen und später mit Eigenschaften "befüllen". Auch hier sieht man keinen Unterschied zu einem leeren assoziativen Arrray:

a3 = {}; // neues leeres Objekt

Anschließend fügen Sie nacheinander Eigenschaften hinzu:

a3.firma = "Audi"; // neue Eigenschaft "firma"
a3.modell = "A6"; // neue Eigenschaft "modell"
a3.kilometerstand = 0; // neue Eigenschaft "kilometerstand"

Bei jeder Zuweisung auf eine unbekannte Eigenschaft, wird die nicht vorhandene Eigenschaft einfach erzeugt.

Bei der Ausgabe mit console.log(a3) sieht das Objekt tatsächlich wie ein assoziativer Array aus:

{firma: "Audi", modell: "A6", kilometerstand: 0}

Eigenschaften über eckige Klammern zugreifen

Da ein Objekt ein assoziativer Array ist, können wir auf die Eigenschaften (= Schlüssel) auch mit Hilfe eckiger Klammern zugreifen. Dies ist neben Punktnotation eine zweite Möglichkeit, auf Eigenschaften zuzugreifen:

console.log(a1['firma']);
console.log(a1['modell']);

Der Vorteil dieser Methode ist, dass die Eigenschaft auch als Variable vorliegen kann. Das werden wir später unter "Eigenschaften auflisten" noch sehen...

Eigenschaften löschen

Sie können auch im Programm eine Eigenschaft wieder löschen:

delete a1.farbe;

Dies betrifft offensichtlich nur das adressierte Objekt.

Eigenschaften testen

Da Sie Objekte jederzeit um Eigenschaften und Methoden erweitern können, benötigen Sie manchmal Mechanismen, um zu testen, ob ein Objekt eine bestimmte Eigenschaft hat.

Mit dem Schlüsselwort in können Sie testen, ob ein Objekt eine bestimmte Eigenschaft oder Methode hat.

Für unser Objekt a1:

let a1 = {
  firma: "VW",
  modell: "Golf",
  kilometerstand: 11032,

  fahre: function() {
    console.log("brumm brumm");
  }
}

Wir können jetzt testen:

console.log('firma' in a1); // true
console.log('fahre' in a1); // true
console.log('kilometer' in a1); // false

Eigenschaften auflisten

Sie können mit einer erweiterten For-Schleifen alle Eigenschaften eines Objekts durchlaufen:

for (p in a1) {
    console.log("Eigenschaft " + p);
}

Jede Eigenschaft wird als String zurückgegeben.

Wenn man zusätzlich den Wert ausgeben möchte, kann man dies über die Notation mit eckigen Klammern tun:

for (p in a1) {
    console.log("Eigenschaft " + p + " hat Wert " + a1[p]);
}

8.6.2 Methoden

Sie können auch Funktionen (Methoden) in das Objekt packen.

let a1 = {
  firma: "VW",
  modell: "Golf",
  kilometerstand: 11032,

  fahre: function() {
    console.log("brumm brumm");
  }
}

Die Methode fahre() gehört zu Objekt a1. Auch hier erfolgt der Aufruf mit Punktnotation:

a1.fahre();

Auch bei Methoden können Sie eine Methode hinzufügen, indem Sie einfach eine nicht vorhandene Eigenschaft mit einer anonymen Funktion belegen:

a2.fahre = function() {
   console.log("vrooooooom");
}

Hier wird auch deutlich, dass die zwei Objekte a1 und a2 unterschiedlich sind: die Methode fahre() hat jeweils andere Ausgaben.

Seien Sie sich auch bewusst, dass niemand Sie daran hindert, die Eigenschaften unterschiedlich zu benennen (bei einem Objekt "firma", beim nächsten "hersteller") oder bei einem Objekt mehr oder weniger Eigenschaften zu definieren. Das ist durchaus ein Nachteil, weil durch Tippfehler etc. unfreiwillig Inkonsistenzen entstehen, denn es gibt keinen "Bauplan", keine Klasse. Vielleicht wird bei der Gelegenheit auch klar, warum die Idee von Klassen keine so schlechte ist.

Methodenverkettung

Wenn eine Methode ein Objekt zurückgibt, kann man direkt die nächste Methode aufrufen. Das nennt man auch Method Chaining.

Bei Strings wird dies gern verwendet. Die Methode substring zerschneidet den String und gibt als Resultat einen neuen String zurück. Wollen wir diesen weiterverarbeiten - z.B. mit toUpperCase - dann können wir die nächste Methode direkt hintenanfügen:

s = "James Bond";
console.log(s.substring(6).toUpperCase()); // BOND

Dieses Prinzip wird auch bei jQuery systematisch genutzt.

8.6.3 Konstruktoren

Wie Sie gesehen haben, können sich in JavaScript schnell Fehler einschleichen, da Sie bei jedem neuen Objekt die Eigenschaften benennen müssen.

Ein Konstruktor ist eine Funktion, die die Herstellung eines Objekts für Sie übernimmt und somit eine falsche Benennung der Eigenschaften verhindert.

Schauen wir uns nochmal folgendes Objekt an:

let a1 = {
  firma: "VW",
  modell: "Golf",
  kilometerstand: 11032,

  fahre: function() {
    console.log("brumm brumm");
  }
}

Wir bauen uns jetzt einen Konstruktor Auto, wo wir genau diese drei Eigenschaften und die eine Methode definieren. Der Konstruktor erwartet drei Parameter für die Initialwerte der drei Eigenschaften:

function Auto(firma, modell, km) {
    this.firma = firma;
    this.modell = modell;
    this.kilometerstand = km;

    this.fahre = function() {
        console.log(this.modell + ": brumm brumm");
    }
}

Beachten Sie, dass man innerhalb der Methoden auf die Eigenschaften über this zugreifen kann.

Ähnlich wie in Java verwenden wir den Konstruktor mit den Schlüsselwort new:

let a2 = new Auto('Skoda', 'Octavia', 10320);

Wir überprüfen den Erfolg den Konstruktoraufrufs mit

console.log(a2);

und sehen die Ausgabe:

Auto {
  firma: 'Skoda',
  modell: 'Octavia',
  kilometerstand: 10320,
  fahre: [Function] }

Der Aufruf der Methode per Punktnotation:

a2.fahre(); // Octavia: brumm brumm

Wenn wir jetzt ein zweites Objekt herstellen, können wir sicher sein, dass die Eigenschaften genauso heißen wir beim ersten Objekt und dass die Funktion "fahre" gleich definiert ist.

let a3 = new Auto('VW', 'Polo', 98001);
a3.fahre(); // Polo: brumm brumm

Wenn man Konstruktoren benutzt, hat man fast schon sowas wie Klassen...

8.7 Code-Analyse

8.7.1 JSHint

JSHint ist ein Softwaretool zum Entdecken von Code-Schwächen und -Fehlern. Man kann das Tool runterladen und auf dem eigenen Rechner installieren, es gibt aber auch eine Webversion, wo Sie direkt im Browser Code per Cut-n-Paste eingeben und checken lassen können:

JSHint online →

Hier sehen Sie eine beispielhafte Code-Eingabe in der Webversion:

JSHint: Codeeingabe

In einem anderen Teil des Fensters sehen Sie Meldungen/Warnungen, die sich auf die angegebenen Zeilen beziehen:

JSHint: Meldungen

JSHint prüft den Code, ohne ihn wirklich laufen zu lassen. Es kann daher keine Fehler entdecken, die erst zur Laufzeit entstehen würden. Man nennt dies daher statische Code-Analyse.

Im Gegensatz dazu gibt es auch Mechanismen, die den Code zur Laufzeit testen. Diese nennt man auch Dynamische Software-Testverfahren. Die wichtigsten dynamischen Verfahren sind Unittests oder Intergrationstests.

8.8 Übungen

8.8.1 Fingerübungen

Funktion

Schreiben Sie eine Funktion durchschnitt, die beliebig viele Zahlen als Parameter bekommen kann (siehe Abschnitt "Variable Parameterzahl") und den Durchschnitt berechnet.

Testen Sie Ihre Funktion mit:

console.log(durchschnitt(10, 20, 30));
console.log(durchschnitt(1, 10));
console.log(durchschnitt(5));

Sie sollten sehen:

20
5.5
5

Array befüllen

Erstellen Sie einen neuen Arrays namens a und befüllen Sie ihn mit ganzen Zahlen von 0 bis 9. Testen Sie mit

console.log(a);

Sie sollten sehen:

[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

String zerlegen und ausgeben mit forEach

Gegeben sei ein String:

s = "Larry;Harry;Mary;Derry";

Zerlegen Sie den String in die vier Namen und geben Sie die Namen einzeln auf der Konsole aus. Verwenden Sie im ersten Schritt eine Schleife und probieren Sie anschließend, das gleiche mit forEach zu realisieren.

Tipp: Verwenden Sie split.

Sie sollten sehen:

Larry
Harry
Mary
Derry

Array filtern mit forEach

Gegeben sei ein Array mit Zahlen:

a = [ 1, 5, -3, 0, -10, 11 ];

Erstellen und befüllen Sie einen neuen Array b, der alle negativen Zahlen aus a enthält. Verwenden Sie forEach.

8.8.2 Arrays

Für die folgenden Übungen sollte man auch Funktionen beherrschen.

(A1) Sortierung prüfen

Schreiben Sie eine Funktion is_sorted, die prüft, ob ein Array sortiert ist (wir betrachten nur Zahlen) und dem entsprechend true oder false zurückgibt.

Testen Sie Ihre Funktion mit

let a = [ 1, 2, 3, 4, 0 ];
let b = [ 10, 100, 1000 ];
let c = [ 3, 2, 1 ];

console.log(is_sorted(a));
console.log(is_sorted(b));
console.log(is_sorted(c));

Sie sollten sehen:

false
true
false

(A2) Strings einsammeln

Schreiben Sie eine Funktion collect_strings, die aus einem beliebigen Array (mit gemischten Inhalten) nur die Strings einsammelt und diese als neuen Array zurückgibt.

Testen Sie Ihre Funktion mit:

let test = [ 5, "Harry", 99, "Sally", 501, 33, "Betty" ];
console.log(collect_strings(test));

Sie sollten sehen:

[ 'Harry', 'Sally', 'Betty' ]

(A3) Filtern

Schreiben Sie die Funktion find, die zwei Parameter bekommt: einen Produktarray und eine Zahl (maximaler Preis). Ein Produktarray besteht aus jeweils Produktbezeichnung und Preis (Zahl). Als Beispiel nehmen Sie:

let products = { 'iPhone XI': 1200, 'Samsung Universe A100': 2000, 'Pixel 5 XXL': 3000 };

Die Funktion sollte alle Produkte auf der Konsole ausgeben, die günstiger sind als die übergebene Zahl.

Testen Sie mit

find(products, 2100);

Sie sollten sehen:

iPhone XI kostet 1200 EUR
Samsung Universe A100 kostet 2000 EUR

8.8.3 Objekte

(O1) Person finden

Jetzt beschäftigen wir uns mit einem assoziativen Array, wo Personen und ihr Alter gespeichert sind:

let data = { "Sam": 33, "Louisa": 63, "Mike": 51, "Lisa": 28, "Conrad": 19, "Lara": 22 };

Schreiben Sie eine Funktion find_closest mit zwei Parametern, einem (assoziativen) Array und einer Zahl. Die Funktion findet die Person, die altermäßig am nächsten an der Zahl dran ist und gibt ein neues Objekt mit den Daten der Person (Name, Alter) zurück (siehe Beispiel unten).

Zum Beispiel sollte der folgende Aufruf

console.log(find_closest(data, 21));

dieses Objekt zeigen:

{ name: 'Lara', age: 22 }

(Wenn es mehrere Personen gibt, die in Frage kommen, kann irgendeine der Möglichkeiten zurückgegeben werden.)

Hinweis: Die Funktion Math.abs liefert den Betrag einer Zahl zurück, was hilfreich bei Differenzen ist.

(O3) Personenobjekte und forEach

Schreiben Sie einen Konstruktor Person mit folgenden Eigenschaften:

Erzeugen Sie einen Array a, den Sie mit vier Objekten füllen:

Durchlaufen Sie den Array mit Hilfe von forEach, so dass folgende Begrüßungen ausgegeben werden:

Guten Tag, Herr Smith
Hallo, Jill
Hallo, Henry
Guten Tag, Frau Leon

Alle Personen, die älter als 18 sind, sollen mit "Guten Tag" begrüßt werden.