Eine kurze Einführung/eine kurze Übersicht über JavaScript für erfahrene Programmierer.
michael.eichberg@dhbw.de, Raum 149B
2.1
Seit 2016 gibt es jährliche Updates (ECMAScript 2016, 2017, 2018, 2019, 2020, 2021, 2022, ...)
Objektorientiert
Protoypische Vererbung
Objekte erben von anderen Objekten
Objekte als allgemeine Container
(Im Grunde eine Vereinheitlichung von Objekten und Hashtabellen.)
seit ES6 werden auch Klassen unterstützt; diese sind aber nur syntaktischer Zucker
Skriptsprache
Loose Typing/Dynamische Typisierung
Load and go-delivery (Lieferung als Text/Quellcode)
Garbage Collected
Single-Threaded
Funktionen sind Objekte erster Klasse
(im Wesentlichen) ein (globaler) Namespace
Syntaktisch eine Sprache der "C"-Familie (viele Ähnlichkeiten zu Java)
Standardisiert durch die ECMA (ECMAScript)
Verwendet ganz insbesondere in Browsern, aber auch Serverseitig (z. B. Node.js) oder in Desktop-Anwendungen (z. B. Electron)
Genutzte Schlüsselworte
function
, async
, await
, return
, yield
, void
break
, continue
, case
, default
, do
, else
, for
, if
, instanceof
, of
, typeof
, switch
, while
throw
, try
, finally
, catch
class
, delete
, extends
, in
, new
, static
, super
, this
const
, let
, var
export
, import
(Sehr vergleichbar mit Java.)
Buchstaben (Unicode), Ziffern, Unterstriche, Dollarzeichen
Ein Identifier darf nicht mit einer Ziffer beginnen
Nameskonventionen:
Klassen beginnen mit einem Großbuchstaben (UpperCamelCase)
Variablen und Funktionen beginnen mit einem Kleinbuchstaben (lowerCamelCase)
Konstanten sind komplett in Großbuchstaben
console
Number, Boolean, Date, BigInt, Math, ...
window
document (bzw. window.document)
alert
navigator
location
module
exports
require
process
crypto
const
und let
)1// Der "Scope" ist auf den umgebenden Block begrenzt.
2// Eine Änderung des Wertes ist möglich.
3let y = "yyy";
45
// Der "Scope" ist auf den umgebenden Block begrenzt.
6// Eine Änderung des Wertes ist nicht möglich.
7const z = "zzz";
89
log("y, z:", y, z);
1011
function doIt() {
12const y = "---";
13log("y, z:", y, z);
14return "";
15}
1617
ilog('"doIt done"', doIt());
18log("y, z:", y, z);
Um diesen und den Code auf den folgenden Folien ggf. mit Hilfe von Node.js auszuführen, muss am Anfang der Datei:
import { ilog, log, done } from "./log.mjs";
und am Ende der Datei:
done();
hinzugefügt werden.
Den entsprechenden Code der Module (log.mjs und später Queue.mjs) finden Sie auf:
https://github.com/Delors/delors.github.io/tree/main/web-javascript/code
1console.log("\nUndefined ----------------------------------------------------");
2let u = undefined;
3console.log("u", u);
45
console.log("\nNumber -------------------------------------------------------");
6let i = 1; // double-precision 64-bit binary IEEE 754 value
7let f = 1.0; // double-precision 64-bit binary IEEE 754 value
8let l = 10_000;
9let binary = 0b1010;
10console.log("0b1010", binary);
11let octal = 0o12;
12console.log("0o12", octal);
13let hex = 0xa;
14console.log("0xA", hex);
15console.log(
16Number.MIN_VALUE,
17Number.MIN_SAFE_INTEGER,
18Number.MAX_SAFE_INTEGER,
19Number.MAX_VALUE,
20);
21let x = NaN;
22let y = Infinity;
23let z = -Infinity;
2425
// Standard Operatoren: +, - , *, /, %, ++, --, **
26// Bitwise Operatoren: &, |, ^, ~, <<, >>, >>>
27// (operieren immer auf dem Ganzzahlwert der Bits)
28console.log("i =", i, "; i++ ", i++); // 1 oder 2?
29console.log("i =", i, "; ++i ", ++i); // 2 oder 3?
30console.log("2 ** 4 === 0 ", 2 ** 4);
31console.log("7 % 3 === ", 7 % 3);
32console.log("1 / 0 === ", 1 / 0);
3334
console.log("\nBigInt -------------------------------------------------------");
35let ib = 1n;
36console.log(100n === BigInt(100));
37console.log(Number.MAX_SAFE_INTEGER + 2102); // 9007199254743092
38console.log(BigInt(Number.MAX_SAFE_INTEGER) + 2102n); // 9007199254743093n
3940
console.log("\nBoolean ------------------------------------------------------");
41let b = true; // oder false
42console.log("Boolean(undefined)", Boolean(undefined)); // true oder false?
43console.log(null == true ? "true" : "false"); // true oder false?
4445
console.log("\n(Quasi-)Logische Operatoren ----------------------------------");
46console.log('1 && "1": ', 1 && "1");
47console.log('null && "1": ', null && "1");
48console.log("null && true: ", null && true);
49console.log("true && null: ", true && null);
50console.log("null && false: ", null && false);
51console.log("{} && true: ", {} && true);
5253
// Neben den Standardoperatoren: ``&&``, ``||``, ``!`` gibt es auch noch ``??``
54// Der ``??``-Operator gibt den rechten Operanden zurück, wenn der linke Operand
55// ``null`` oder ``undefined`` ist. Andernfalls gibt er den linken Operanden
56// zurück.
57// ``??`` ist der *nullish coalescing operator (??)*
58// Falls der linke Wert null oder undefined ist, dann ist er vergleichbar zu ||
59console.log('1 ?? "1": ', 1 ?? "1");
60console.log('null ?? "1": ', null ?? "1");
61console.log("null ?? true: ", null ?? true);
62console.log("true ?? null: ", true ?? null);
63console.log("null ?? false: ", null ?? false);
64console.log("{} ?? true: ", {} ?? true);
6566
console.log('undefined ?? "1": ', undefined ?? "1");
67console.log('undefined ?? "1": ', undefined ?? "1");
68console.log("undefined ?? true: ", undefined ?? true);
69console.log("true ?? undefined: ", true ?? undefined);
70console.log("undefined ?? false: ", undefined ?? false);
71console.log("undefined ?? undefined: ", undefined ?? undefined);
7273
console.log("\nStrings ------------------------------------------------------");
74let _s = "42";
75console.log("Die Antwort ist " + _s + "."); // String concatenation
76console.log(`Die Antwort ist ${_s}.`); // Template literals (Template strings)
77// multiline Strings
78console.log(`
79Die Antwort mag ${_s} sein,
80aber was ist die Frage?`);
8182
console.log(String(42)); // "42"
8384
// ACHTUNG Objekte und Errors am Besten direkt an log übergeben,
85// damit die Objekteigenschaften ausgeben werden.
86console.log("State: " + { a: "abc" }, { a: "abc" });
8788
console.log("\nObjekte ------------------------------------------------------");
89let emptyObject = null;
90let anonymousObj = {
91i: 1,
92u: { j: 2, v: { k: 3 } },
93toString: function () {
94return "anonymousObj";
95},
96"?": "question mark",
97};
98// Zugriff auf die Eigenschaften eines Objekts
99anonymousObj.j = 2; // mittels Bezeichner ("j") (eng. Identifier)
100anonymousObj["j"] = 4; // mittels String ("j")
101anonymousObj["k"] = 3;
102console.log("anonymousObj: ", anonymousObj);
103console.log("anonymousObj.toString(): ", anonymousObj.toString());
104105
// delete dient dem Löschen von Eigenschaften:
106delete anonymousObj["?"];
107delete anonymousObj.toString;
108console.log("anonymousObj.toString() [original]", anonymousObj.toString());
109110
// Der Chain-Operator "?." kann verwendet werden, um auf Eigenschaften
111// (Properties) von Objekten zuzugreifen, ohne dass eine Fehlermeldung
112// ausgegeben wird, wenn eine (höher-liegende) Eigenschaft nicht definiert ist.
113// Besonders nützlich beim Verarbeiten von komplexen JSON-Daten.
114console.log("anonymousObj.u?.v.k", anonymousObj.u?.v.k);
115console.log("anonymousObj.u.v?.k", anonymousObj.u.v?.k);
116console.log("anonymousObj.u.v?.z", anonymousObj.u.v?.z);
117console.log("anonymousObj.u.q?.k", anonymousObj.u.q?.k);
118console.log("anonymousObj.p?.v.k", anonymousObj.p?.v.k);
119120
// Nützliche Zuweisungen, um den Fall undefined und null gemeinsam zu behandeln:
121anonymousObj.name ||= "Max Mustermann";
122123
console.log("\nDate ---------------------------------------------------------");
124let date = new Date("8.6.2024"); // ACHTUNG: Locale-Settings
125console.log(date);
126127
console.log("\nFunktionen sind auch Objekte ---------------------------------");
128let func = function () {
129return "Hello World";
130};
131console.log(func, func());
132133
console.log("\nArrays -------------------------------------------------------");
134let temp = undefined;
135let $a = [1];
136console.log("let $a = [1]; $a, $a.length", $a, $a.length);
137$a.push(2); // append
138console.log("$a.push(2); $a", $a);
139temp = $a.unshift(0); // "prepend" -> return new length
140console.log("temp = $a.unshift(0); temp, $a", temp, $a);
141temp = $a.shift(); // remove first element -> return removed element
142console.log("temp = $a.shift(); temp, $a", temp, $a);
143// Um zu prüfen ob eine Datenstruktur ein Array ist:
144console.log("Array.isArray($a)", Array.isArray($a));
145console.log("Array.isArray({})", Array.isArray({}));
146console.log("Array.isArray(1)", Array.isArray(1));
147148
console.log("\nSymbols ------------------------------------------------------");
149let sym1 = Symbol("1"); // a unique and immutable primitive value
150let sym2 = Symbol("1");
151let obj1Values = { sym1: "value1", sym2: "value2" };
152console.log(obj1Values);
153console.log(`sym1 in ${JSON.stringify(obj1Values)}: `, sym1 in obj1Values);
154let obj2Values = { [sym1]: "value1", [sym2]: "value2" };
155console.log(obj2Values);
156console.log(`sym1 in ${JSON.stringify(obj2Values)}: `, sym1 in obj2Values);
157console.log(obj1Values, " vs. ", obj2Values);
158159
console.log({ sym1: "this", sym1: "that" }); // ??? { sym1: "that" }
160console.log("sym1 == sym2", sym1 == sym2);
1// Die Funktionsdeklaration der Funktion "hello" ist "hochgezogen" (🇺🇸 hoisted)
2// und kann hier verwendet werden.
3hello("Michael");
45
function hello(person = "World" /* argument with default value */) {
6log(`fun: Hello ${person}!`);
7}
8hello();
910
waitOnInput();
1112
const helloExpr = function () {
13// Anonymer Funktionsausdruck
14log("expr: Hello World!");
15};
1617
// Arrow Functions
18const times3 = (x) => x * 3;
19log("times3(5)", times3(5)); // 15
2021
const helloArrow = () => log("arrow: Hello World!");
22const helloBigArrow = () => {
23const s = "Hello World!";
24log("arrow: " + s);
25return s;
26};
27helloExpr();
28helloArrow();
2930
var helloXXX = function helloYYY() {
31// benannter Funktionsausdruck
32// "helloYYY" ist _nur_ innerhalb der Funktion sichtbar und verwendbar
33// "arguments" ist ein Arrays-vergleichbares Objekt
34// und enthält alle Argumente der Funktion
35log(`Hello: `, ...arguments); // "..." ist der "Spread Operator"
36};
37helloXXX("Michael", "John", "Jane");
3839
waitOnInput();
4041
log("\nFunction Arguments ---------------------------------------------------");
4243
function sum(...args) {
44// rest parameter: ...
45log("typeof args: " + typeof args + "; isArray: " + Array.isArray(args));
46log("args: " + args);
47log("args:", ...args); // die Arraywerte werden als einzelne Args. übergeben
48return args.reduce((a, b) => a + b, 0); // function nesting
49}
50log("sum(1, 2, 3, 4, 5)", sum(1, 2, 3, 4, 5)); // 15
51log("sum()", sum());
5253
log("\nGenerator Functions --------------------------------------------------");
54/* Generator Functions */
55function* fib() {
56// generator
57let a = 0,
58b = 1;
59while (true) {
60yield a;
61[a, b] = [b, a + b];
62}
63}
64const fibGen = fib();
65log(fibGen.next().value); // 0
66log(fibGen.next().value); // 1
67log(fibGen.next().value); // 1
68log(fibGen.next().value); // 2
69/* Will cause an infinite loop:
70for (const i of fib()) console.log(i);
71// 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 ... */
Voraussetzung: Installieren Sie Node.js (http://nodejs.org/).
Hello World in Node.js
Starten Sie die Konsole/Terminal und schreiben Sie ein einfaches JavaScript Programm, das "Hello World" ausgibt.
Hello World auf der JavaScript Console
Starten Sie einen Browser und aktivieren Sie die JavaScript Console in den Entwicklerwerkzeugen. Schreiben Sie ein einfaches JavaScript Programm, das "Hello World" ausgibt.
Prototyping mit der JavaScript Konsole
Schreiben Sie ein kurzes JavaScript Programm, das programmatisch zum Ende des Dokuments scrollt.
Hinweise:
das von document.body
referenziert HTML Element enthält den gesamten Inhalt des Dokuments
die aktuellen Abmaße des Dokuments können Sie mit der Funktion window.getComputedStyle(<HTML Element>).height
ermitteln; geben Sie den Wert auf der Konsole aus bevor Sie das Dokument scrollen; was fällt Ihnen auf?
um zu scrollen, können Sie window.scrollTo(x,y)
verwenden
um den Integer Wert eines Wertes in Pixeln zu bestimmen, können Sie parseInt
verwenden.
(Sei der String: "100px"
, dann liefert parseInt
, den Wert 100
).
1// Gleichheit == // mit Typumwandlung (auch bei <, >, <=, >=)
23
// strikt gleich === // ohne Typumwandlung
4// strike Ungleichheit !== // ohne Typumwandlung
56
log('1 == "1": ', 1 == "1");
7log('1 === "1": ', 1 === "1");
8log("1.0 == 1: ", 1 == 1.0);
9log("1.0 === 1: ", 1 === 1.0);
10log("1 === 1n: ", 1 === 1n); // 1n ist ein bigint mit den Wert 1
11log("1 == 1n: ", 1 == 1n);
12log('1 < "1"', 1 < "1");
13log('0 < "1"', 0 < "1");
14log('0 <= "0"', 0 <= "0");
15log('"abc" <= "d"', "abc" <= "d");
1617
log('"asdf" === "as" + "df"', "asdf" === "as" + "df"); // unlike Java!
1819
log("NaN === NaN: ", NaN === NaN);
20log("NaN == NaN: ", NaN == NaN);
21log("null === NaN: ", null === NaN);
22log("null == NaN: ", null == NaN);
23log("null === null: ", null === null);
24log("null == null: ", null == null);
25log("undefined === undefined: ", undefined === undefined);
26log("undefined == undefined: ", undefined == undefined);
27log("null === undefined: ", null === undefined);
28log("null == undefined: ", (null == undefined) + "!");
2930
const a1 = [1, 2, 3];
31const a2 = [1, 2, 3];
32log("const a1 = [1, 2, 3]; a1 == [1, 2, 3]: ", a1 == [1, 2, 3]);
33log("const a1 = [1, 2, 3]; a1 == a1: ", a1 == a1);
34log("const a1 = [1, 2, 3]; a1 === a1: ", a1 === a1);
35log("const a1 = [1, 2, 3]; const a2 = [1, 2, 3]; a1 === a2: ", a1 === a2);
36log("const a1 = [1, 2, 3]; const a2 = [1, 2, 3]; a1 == a2: ", a1 == a2);
37log(
38"flatEquals(a1,a2):",
39a1.length == a2.length && a1.every((v, i) => v === a2[i]),
40);
4142
let firstJohn = { person: "John" };
43show('let firstJohn = { person: "John" };');
44let secondJohn = { person: "John" };
45show('let secondJohn = { person: "John" };');
46let basedOnFirstJohn = Object.create(firstJohn);
47show("let basedOnFirstJohn = Object.create(firstJohn)");
48log("firstJohn == firstJohn: ", firstJohn == firstJohn);
49log("firstJohn === secondJohn: ", firstJohn === secondJohn);
50log("firstJohn == secondJohn: ", firstJohn == secondJohn);
51log("firstJohn === basedOnFirstJohn: ", firstJohn === basedOnFirstJohn);
52log("firstJohn == basedOnFirstJohn: ", firstJohn == basedOnFirstJohn);
5354
{
55const obj = {
56name: "John",
57age: 30,
58city: "Berlin",
59};
60show("Typtests und Feststellung des Typs:");
61log("typeof obj", typeof obj);
62log("obj instanceof Object", obj instanceof Object);
63log("obj instanceof Array", obj instanceof Array);
6465
// ⚠️ Das Array Objekt ist Kontextabhängig:
66//
67// iframe.contentWindow.Array !== window.Array;
68//
69// D.h. ein instanceof Check liefert ggf. unerwartete Resultate
70}
71{
72const obj = { a: "lkj" };
73const obj2 = Object.create(obj);
74log("obj2 instanceof obj.constructor", obj2 instanceof obj.constructor);
75}
7677
log("\n?-Operator/if condition and Truthy and Falsy Values:");
78log('""', "" ? "is truthy" : "is falsy");
79log("f()", (() => {}) ? "is truthy" : "is falsy");
80log("Array ", Array ? "is truthy" : "is falsy");
81log("obj ", {} ? "is truthy" : "is falsy");
82log("undefined ", undefined ? "is truthy" : "is falsy");
83log("null ", null ? "is truthy" : "is falsy");
84log("0", 0 ? "is truthy" : "is falsy");
85log("1", 1 ? "is truthy" : "is falsy");
NaN (Not a Number) repräsentiert das Ergebnis einer Operation die keinen sinnvollen Wert hat. Ein Vergleich mit NaN ist immer false
. Um zu überprüfen, ob ein Wert NaN ist muss isNaN(<Value>)
verwendet werden.
1const arr = [1, 3, 4, 7, 11, 18, 29];
23
log("if-else_if-else:");
4if (arr.length == 7) {
5ilog("arr.length == 7");
6} else if (arr.length < 7) {
7ilog("arr.length < 7");
8} else {
9ilog("arr.length > 7");
10}
1112
log("\nswitch (integer value):");
13switch (arr.length) {
14case 7:
15ilog("arr.length == 7");
16break;
17case 6:
18ilog("arr.length == 6");
19break;
20default:
21ilog("arr.length != 6 and != 7");
22}
2324
log("\nswitch (string value):");
25switch ("foo") {
26case "bar":
27ilog("it's bar");
28break;
29case "foo":
30ilog("it's foo");
31break;
32default:
33ilog("not foo, not bar");
34}
3536
log("\nswitch (integer - no type conversion):");
37switch (
381 // Vergleich auf strikte Gleichheit (===)
39) {
40case "1":
41ilog("string(1)");
42break;
43case 1:
44ilog("number(1)");
45break;
46}
4748
ilog("\nfor-continue:");
49for (let i = 0; i < arr.length; i++) {
50const v = arr[i];
51if (v % 2 == 0) continue;
52log(v);
53}
5455
ilog("\n(for)-break with label:");
56outer: for (let i = 0; i < arr.length; i++) {
57for (let j = 0; j < i; j++) {
58if (j == 3) break outer;
59log(arr[i], arr[j]);
60}
61}
6263
ilog("\nin (properties of Arrays; i.e. the indexes):");
64for (const key in arr) {
65log(key, arr[key]);
66}
6768
ilog("\nof (values of Arrays):");
69for (const value of arr) {
70log(value);
71}
7273
ilog("\nArray and Objects - instanceof:");
74log("arr instanceof Object", arr instanceof Object);
75log("arr instanceof Array", arr instanceof Array);
7677
const obj = {
78name: "John",
79age: 30,
80city: "Berlin",
81};
8283
ilog("\nin (properties of Objects):");
84for (const key in obj) {
85log(key, obj[key]);
86}
8788
/* TypeError: obj is not iterable
8990
for (const value of obj) {
91log(value);
92}
93*/
9495
{
96ilog("\nIteration über Iterables (here: Map):");
97const m = new Map();
98m.set("name", "Elisabeth");
99m.set("alter", 50);
100log("Properties of m: ");
101for (const key in m) {
102log(key, m[key]);
103}
104log("Key-Values of m: ");
105for (const [key, value] of m) {
106log(key, value);
107}
108}
109110
{
111ilog("\nWhile Loop: ");
112let c = 0;
113while (c < arr.length) {
114const v = arr[c];
115if (v > 10) break;
116log(v);
117c++;
118}
119}
120121
{
122ilog("\nDo-While Loop: ");
123let c = 0;
124do {
125log(arr[c]);
126c++;
127} while (c < arr.length);
128}
Die Tatsache, dass insbesondere null
als auch undefined
falsy sind, wird of in Bedingungen ausgenutzt (z. B., if (!x)...
).
1console.log("try-catch-finally - Grundlagen ---------------------------------");
23
try {
4let i = 1 / 0; // Berechnungen erzeugen nie eine Exception
5console.log("i", i);
6} catch {
7console.error("console.log failed");
8} finally {
9console.log("computation finished");
10}
1112
console.log("Programmierfehler behandeln ------------------------------------");
13try {
14const obj = {};
15obj = { a: 1 };
16} catch ({ name, message }) {
17console.error(message);
18} finally {
19console.log("object access finished");
20}
2122
console.log("Handling of a specific error -----------------------------------");
23try {
24throw new RangeError("out of range");
25} catch (error) {
26if (error instanceof RangeError) {
27const { name, message } = error;
28console.error("a RangeError:", name, message);
29} else {
30throw error;
31}
32} finally {
33console.log("error handling finished");
34}
In JavaScript können während der Laufzeit Fehler auftreten, die (z. B.) in Java während des kompilierens erkannt werden.
removeNthElement
Implementieren Sie eine Funktion, die ein Array übergeben bekommt und ein neues Array zurückgibt in dem jedes n-te Element nicht vorkommt.
Beispiel: removeNthElement([1,2,3,4,5,6,7], 2)
\(\Rightarrow\) [1,3,5,7]
Schreiben Sie Ihren Code in eine JavaScript Datei und führen Sie diese mit Hilfe von Node.js aus.
Testen Sie Ihre Funktion mit verschiedenen Eingaben und lassen Sie sich das Ergebnis ausgeben (z. B. console.log(removeNthElement([1,2,3,4,5,6,7],2))
)!
removeNthElement mit Fehlerbehandlung
Erweitern Sie die Implementierung von removeNthElement
so, dass die Funktion einen Fehler wirft, wenn das übergebene Array kein Array ist oder wenn der zweite Parameter kein positiver Integer ist.
Testen Sie alle Fehlerzustände und fangen Sie die entsprechenden Fehler ab (catch
) und geben Sie die Nachrichten aus.
Einfacher RPN Calculator
Implementieren Sie einen einfachen RPN (Reverse Polish Notation) Calculator, der eine Liste von Zahlen und Operatoren (+
, -
, *
, /
) als Array entgegennimmt und das Ergebnis berechnet.
Nutzen Sie keine if
oder switch
Anweisung, um die Operatoren zu unterscheiden. Nutzen Sie stattdessen ein Objekt. Sollte der Operator unbekannt sein, dann geben Sie eine entsprechende Fehlermeldung aus.
Beispiel: eval([2,3,"+",4,"*"])
\(\Rightarrow\) 20
1let y = "yyy"; // wie zuvor
2const z = "zzz";
34
// Der Gültigkeitsbereich von var ist die umgebende Funktion oder der
5// globale Gültigkeitsbereich.
6// Die Definition ist hochgezogen (eng. "hoisted") (initialisiert mit undefined);
7var x = "xxx";
89
function sumIfDefined(a, b) {
10// ⚠️ Der folgende Code ist NICHT empfehlenswert!
11// Er dient der Visualisierung des Verhaltens von var.
12if (parseInt(a)) {
13var result = parseInt(a);
14} else {
15result = 0;
16}
17const bVal = parseFloat(b);
18if (bVal) {
19result += bVal;
20}
21return result;
22}
2324
ilog("sumIfDefined()", sumIfDefined()); // 0
25ilog("sumIfDefined(1)", sumIfDefined(1)); // 1
26ilog("sumIfDefined(1, 2)", sumIfDefined(1, 2)); // 3
27ilog('sumIfDefined(1, "2")', sumIfDefined(1, "2")); // 3
28ilog("undefined + 2", undefined + 2);
29ilog('sumIfDefined(undefined, "2")', sumIfDefined(undefined, "2")); // 2
3031
function global_x() {
32ilog("global_x():", x, y, z);
33}
3435
function local_var_x() {
36ilog("local_var_x(): erste Zeile (x)", x);
3738
var x = 1; // the declaration of var is hoisted, but not the initialization
39let y = 2;
40const z = 3;
4142
ilog("local_var_x(): letzte Zeile (x, y, z)", x, y, z); // 1 2 3
43}
4445
global_x();
46local_var_x();
4748
ilog("nach global_x() und local_var_x() - x, y, z:", x, y, z);
4950
51
// Hier, ist nur die Variablendeklaration (helloExpr) "hoisted", aber nicht
52// die Definition. Daher kann die Funktion nicht vorher im Code aufgerufen
53// werden!
54try {
55helloExpr();
56} catch ({error, message}) {
57log("calling helloExpr() failed:", error, "; message: ", message);
58}
59var helloExpr = function () {
60log("expr: Hello World!");
61};
62// ab jetzt funktioniert es
63helloExpr();
1log("Array Destructuring:");
23
let [val1, val2] = [1, 2, 3, 4];
4ilog("[val1, val2] = [1, 2, 3, 4]:", "val1:", val1, ", val2:", val2); // 1
56
log("Object Destructuring:");
78
let { a, b } = { a: "aaa", b: "bbb" };
9ilog('let { a, b } = { a: "aaa", b: "bbb" }: ', "a:", a, ", b:", b); // 1
1011
{
12let { a: x, b: y } = { a: "aaa", b: "bbb" };
13ilog('let { a: x, b: y } = { a: "aaa", b: "bbb" }: ', "x:", x, ", y:", y); // 1
14}
15{
16let { a: x, c: y } = { a: "aaa", b: "bbb" };
17ilog('let { a: x, c: y } = { a: "aaa", b: "bbb" }: ', "x:", x, ", y:", y); // 1
18}
1920
let { a: u, b: v, ...w } = { a: "+", b: "-", c: "*", d: "/" };
21ilog(
22'let { a: u, b: v, ...w } = { a: "+", b: "-", c: "*", d: "/" }:',
23"u:",
24u,
25", v:",
26v,
27", w:",
28JSON.stringify(w), // just for better readability/comprehension
29);
3031
let { k1, k2 } = { a: "a", b: "b" };
32ilog('let { k1, k2 } = { a: "a", b: "b" }:', "k1:", k1, ", k2:", k2);
33// "undefined undefined", weder k1 noch k2 sind definiert
1const someJSON = `{
2"name": "John",
3"age": 30,
4"cars": {
5"American": ["Ford"],
6"German": ["BMW", "Mercedes", "Audi"],
7"Italian": ["Fiat","Alfa Romeo", "Ferrari"]
8}
9}`;
1011
// JSON.parse(...) JSON String => JavaScript Object
12const someObject = JSON.parse(someJSON);
1314
someObject.age = 31;
15someObject.cars.German.push("Porsche");
16someObject.cars.Italian.pop();
17console.log(someObject);
1819
// JSON.stringify(...) JavaScript Object => JSON String
20console.log(JSON.stringify(someObject, null, 2));
JSON requires that keys must be strings and strings must be enclosed in double quotes.
Eingebaute Unterstützung basierend auf entsprechenden Literalen (Strings in "/") und einer API
inspiriert von der Perl Syntax
Methoden auf regulären RegExps: test
(e.g., <RegExp>.test(String)
).
Methoden auf Strings, die reguläre Ausdrücke verarbeiten: search
, match
, replace
, split
, ...
1{
2const p = /.*[1-9]+H/; // a regexp
3console.log(p.test("ad13H"));
4console.log(p.test("ad13"));
5console.log(p.test("13H"));
6}
7{
8const p = /[1-9]+H/g;
9const s = "1H, 2H, 3P, 4C";
10console.log(s.match(p));
11console.log(s.replace(p, "XX"));
12}
1class Figure {
2calcArea() {
3throw new Error("calcArea is not implemented");
4}
5}
6class Rectangle extends Figure {
7height;
8width;
910
constructor(height, width) {
11super();
12this.height = height;
13this.width = width;
14}
1516
calcArea() {
17return this.height * this.width;
18}
1920
get area() {
21return this.calcArea();
22}
2324
set area(value) {
25throw new Error("Area is read-only");
26}
27}
2829
const r = new Rectangle(10, 20);
30console.log("r instanceof Figure", r instanceof Figure); // true
31console.log(r.width);
32console.log(r.height);
33console.log(r.area); // 200
3435
try {
36r.area = 300; // Error: Area is read-only
37} catch (e) {
38console.error(e.message);
39}
Queue.mjs exportiert die Klasse Queue
1/* Modul für den Datentyp Warteschlange (Queue). */
2export class Queue {
3#last = null; // private field
4#first = null;
5constructor() {} // "default constructor"
6enqueue(elem) {
7if (this.#first === null) {
8const c = { e: elem, next: null };
9this.#first = c;
10this.#last = c;
11} else {
12const c = { e: elem, next: null };
13this.#last.next = c;
14this.#last = c;
15}
16}
17dequeue() {
18if (this.#first === null) {
19return null;
20} else {
21const c = this.#first;
22this.#first = c.next;
23return c.e;
24}
25}
26head() {
27if (this.#first === null) {
28throw new Error("Queue is empty");
29} else {
30return this.#first.e;
31}
32}
33last() {
34if (this.#first === null) {
35throw new Error("Queue is empty");
36} else {
37return this.#last.e;
38}
39}
40isEmpty() {
41return this.#first === null;
42}
43}
log.mjs verwendet (import
) die Klasse Queue und exportiert Funktionen zum Loggen
1import { Queue } from "./Queue.mjs"; // import des Moduls "Queue.mjs"
23
const messages = new Queue();
45
export function log(...message) {
6if (messages.isEmpty()) {
7messages.enqueue(message);
8} else {
9message.unshift("\n");
10messages.last().push(...message);
11}
12}
ECMAScript Module verwenden immer den strict mode.
Import Statements erlauben das selektierte importieren als auch das Umbenennen von importierten Elementen (z. B., import { Queue as Q } from "./Queue.mjs";
).
this
ist ein "zusätzlicher" Parameter, dessen Wert von der aufrufenden Form abhängt
this
ermöglicht den Methoden den Zugriff auf ihr Objekt
this
wird zum Zeitpunkt des Aufrufs gebunden (außer bei Arrow-Funktionen, da erfolgt die Bindung zum Zeitpunkt der Definition und es wird das this aus dem umgebenden Context geerbt.)
1//"use strict";
23
function counter() {
4// console.log(this === globalThis); // true
5if (this.count)
6// this is the global object if we don't use strict mode
7this.count++;
8else {
9this.count = 1;
10}
1112
return this.count;
13}
1415
const counterExpr = function () {
16if (this.count) this.count++;
17else {
18this.count = 1;
19}
2021
return this.count;
22};
2324
const counterArrow = () => {
25console.log(this);
26console.log(this === globalThis);
27this.count = this.count ? this.count + 1 : 1;
28return this.count;
29};
3031
console.log("\nCounter");
32console.log(counter()); // 1
33console.log(counter()); // 2
34console.log(`globalThis.count (${globalThis.count})`);
3536
console.log("\nCounterExpression");
37console.log(counterExpr()); // 3
38console.log(counterExpr()); // 4
3940
console.log("\nCounter");
41const obj = {}; // empty
42console.log(counter.apply(obj)); // 1 - we pass in obj as "this"!
43console.log(counterExpr.apply(obj)); // 2
4445
console.log(`\nCounterArrow (${this.count})`);
46console.log(counterArrow.apply(obj)); // 1
47console.log(counterArrow.apply(undefined)); // 2
48console.log(counterArrow.apply()); // 3
49console.log(counterArrow.apply(obj)); // 4
50console.log(counterArrow.apply({})); // 5
5152
console.log("\nCounter (global)");
53console.log(counter());
54console.log(counterExpr());
1function add(x, y) {
2return x + y;
3}
45
// Partial function application:
6const add2 = add.bind(null, 2); // "null" is the value of "this"
7console.log(add2(3));
89
10
function addToValue(b) {
11return this.x + b;
12}
13console.log(addToValue.call({x : 0}, -101));
Verwendung von Object.create
zur Initialisierung der Prototype Chain:
1const p = { s : "p" };
2const c = Object.create(p);
3const gc = Object.create(c);
Verwendung der Eigenschaften von Prototypen:
1const p = { s : "p" };
2const c = Object.create(p);
3const gc = Object.create(c);
4gc.t = "q";
5gc.s = "gc"
6console.log(gc.s); // gc
7delete gc.s;
8console.log(gc.s); // p
Pseudoclassical Inheritance
1// constructor for Person objects:
2function Person(name, title){ this.name = name; this.title = title; }
3Person.prototype.formOfAddress = function (){
4const foa = "Dear ";
5if(this.title){ foa += this.title+" "; }
6return foa + this.name;
7}
8function Student(name, title, id, email) {
9Person.call(this, name, title); // super constructor call
10this.id = id;
11this.email = email;
12}
13Student.prototype = Object.create(Person.prototype);
14Student.prototype.constructor = Student;
1516
const aStudent = new Student("Emily Xi", "Mrs.", 12441, 'emily@xi.de');
Objektabhängigkeiten
1function Person(name, title){ … }
2Person.prototype.formOfAddress = function (){ … }
34
function Student(name, title, id, email) { … }
5Student.prototype = Object.create(Person.prototype);
6Student.prototype.constructor = Student;
78
const p = new Person(…);
9const s = new Student(…);
Die Eigenschaft prototype
einer Funktion (F
) verweist auf das Objekt, dass als Prototype (__proto__
) verwendet wird, wenn die Funktion als Konstruktor verwendet wird. D. h. im Falle einer Instantiierung von F
(d. h. const newF = new F()
) wird das Objekt, das durch F.prototype
referenziert wird, als Prototype (newF.__proto__
) des neu erstellten Objekts (newF
) verwendet.
1// Prototypen
2console.log("{}.__proto__: ",{}.__proto__);
3console.log("Array.prototype: ",Array.prototype);
4console.log("Array.prototype.__proto__: ",Array.prototype.__proto__);
5console.log("Object.prototype: ",Object.prototype);
6console.log("Object.__proto__: ",Object.__proto__);
78
let o = { created: "long ago" };
9var p = Object.create(o);
10console.log("Object.getPrototypeOf(o): " + Object.getPrototypeOf(o));
11console.log("o.isPrototypeOf(p):" + o.isPrototypeOf(p));
12console.log("Object.prototype.isPrototypeOf(p): " + Object.prototype.isPrototypeOf(p));
1let a = [1, 10, 100, 1000];
2try { console.log(a.fold()); } catch (error) {
3console.log("error: ", error.message);
4}
56
// - ATTENTION! ----------------------------------------------------------------
7// ADDING FUNCTIONS TO Array.prototpye IS NOT RECOMMENDED! IF ECMAScript
8// EVENTUALLY ADDS THIS METHOD (I.E. fold) TO THE PROTOTYPE OF ARRAY OBJECTS,
9// IT MAY CAUSE HAVOC.
10Array.prototype.fold = function (f) {
11if (this.length === 0) {
12throw new Error("array is empty");
13} else if (this.length === 1) {
14return this[0];
15} else {
16let result = this[0];
17for (let i = 1; i < this.length; i++) {
18result = f(result, this[i]);
19}
20return result;
21}
22};
2324
console.log(a.fold((u, v) => u + v));
1<html lang="en">
2<head>
3<meta charset="utf-8" />
4<meta name="viewport" content="width=device-width, initial-scale=1.0" />
5<title>DOM Manipulation with JavaScript</title>
6<script>
7function makeScriptsEditable() {
8const scripts = document.getElementsByTagName("script");
9for (const scriptElement of scripts) {
10scriptElement.contentEditable = false;
11const style = scriptElement.style;
12style.display = "block";
13style.fontFamily = "menlo, monospaced";
14style.whiteSpace = "preserve";
15style.padding = "0.25em";
16style.backgroundColor = "lightyellow";
17}
18}
19</script>
20</head>
21<body>
22<h1>DOM Manipulation with JavaScript</h1>
23<p id="demo">This is a paragraph.</p>
24<button
25type="button"
26onclick="
27document.getElementById('demo').style.color = 'red';
28makeScriptsEditable();
29document.querySelector('button').style.display = 'none';"
30>
31Magic!
32</button>
3334
<script>
35const demoElement = document.getElementById("demo");
36const style = demoElement.style;
37demoElement.addEventListener("mouseover", () => (style.color = "green"));
38demoElement.addEventListener("mouseout", () => (style.color = "unset"));
39</script>
4041
<p>Position der Mouse: <span id="position"></span></p>
42<script>
43window.addEventListener("mousemove", () => {
44document.getElementById("position").innerHTML =
45`(${event.clientX}, ${event.clientY})`;
46});
47</script>
48</body>
49</html>
1// "express" and "cors" are CommonJS modules, which requires us to use the
2// "default import" syntax.
3import express from "express";
45
// Cross-Origin Resource Sharing (CORS); This is required to allow the browser
6// using a different domain to load the HTML to make requests to this server.
7// I. e., we can use the HTML file from the "web-javascript" project to make
8// requests to this server.
9import cors from "cors";
10const APP_PORT = 5080;
1112
const app = express();
1314
app.get("/users", cors(), function (req, res) {
15res.set("Content-Type", "application/json");
16res.end(`{
17"user1" : {
18"name" : "dingo",
19"password" : "1234",
20"profession" : "chef",
21"id": 1
22},
2324
"user2" : {
25"name" : "ringo",
26"password" : "asdf",
27"profession" : "boss",
28"id": 3
29}
30}`);
31});
3233
app.listen(APP_PORT, function () {
34console.log(`Users App @ http://127.0.0.1:${APP_PORT}`);
35});
Express ist ein minimalistisches Web-Framework für Node.js, das die Entwicklung von Webanwendungen vereinfacht. Die Installation kann über einen Packagemanager erfolgen.
Installieren Sie (z. B.) pnpm (https://pnpm.io/) und nutzen Sie danach pnpm, um die benötigten Module zu installieren:
$ pnpm init
$ pnpm install express
Danach starten Sie Ihren Server mit:
node --watch UsersServer.mjs
1<html lang="en">
2<head>
3<meta charset="utf-8" />
4<meta name="viewport" content="width=device-width, initial-scale=1.0" />
5<title>Eventhandling</title>
6</head>
7<body>
8<script>
9/* Using Promises:
10function getUsers() {
11fetch('http://127.0.0.1:4080/users')
12.then(response => response.json())
13.then(users => {
14const usersElement = document.getElementById('users');
15usersElement.innerText = JSON.stringify(users);
16});
17}
18*/
1920
/* Using async/await: */
21async function getUsers() {
22let response = await fetch("http://127.0.0.1:5080/users");
23let users = await response.json();
24const usersElement = document.getElementById("users");
25usersElement.innerText = JSON.stringify(users);
26}
27</script>
2829
<div id="users"></div>
30<button onclick="getUsers()">Get Users</button>
31</body>
32</html>
Im Folgenden verwenden wir zur Client-/Server-Kommunikation insbesondere Websockets.
Server
1const express = require('express');
2const app = express();
34
const expressWs = require('express-ws')(app);
56
let clients = 0;
7let playerWSs = [];
89
let adminWS = null;
10let answersCount = 0;
11let correctAnswersCount = 0;
1213
app.use(express.static('.')); // required to serve static files
1415
16
function sendCurrentPlayers() {
17if (adminWS && playerWSs.length > 0) {
18allPlayers = playerWSs
19.filter(player => player.name)
20.map(player => { return { "id": player.id, "name": player.name } })
21console.log("Sending current players: " + JSON.stringify(allPlayers));
22adminWS.send(JSON.stringify({ "type": "players", "players": allPlayers }));
23}
24}
2526
function sendNextQuestion() {
27answersCount = 0;
28correctAnswersCount = 0;
29const question = "What is the capital of France?";
30const answers = ["Paris", "London", "Berlin", "Madrid"];
31const correct = "Paris";
3233
const nextQuestion = JSON.stringify({
34"type": "question",
35"question": question,
36"answers": ["Paris", "London", "Berlin", "Madrid"]
37})
38playerWSs.forEach(player => player.ws.send(nextQuestion));
39adminWS.send(JSON.stringify({
40"type": "question",
41"question": question,
42"answers": answers,
43"correct": correct
44}));
45}
4647
function sendResults() {
48const results = playerWSs.map(player => {
49return { "id": player.id, "name": player.name, "wins": player.wins }
50});
51const sortedResults = results.sort((a, b) => b.wins - a.wins);
52const resultsMsg = JSON.stringify({
53"type": "results",
54"results": sortedResults
55});
56playerWSs.forEach(player => player.ws.send(resultsMsg));
57adminWS.send(resultsMsg);
5859
}
6061
62
function handleAnswer(clientId, answer) {
63const correct = answer.answer === "Paris";
64const player = playerWSs.find(player => player.id === clientId);
65if (correct) {
66if (correctAnswersCount === 0) {
67player.wins++;
68}
69correctAnswersCount++;
70}
71answersCount++;
72if (answersCount === playerWSs.length) {
73// sendNextQuestion();
74sendResults();
75} else {
76adminWS.send(JSON.stringify({
77"type": "answers",
78"count": answersCount,
79"correctAnswersCount": correctAnswersCount
80}));
81}
82}
8384
85
app.ws('/player', function (ws, request) {
86const clientId = clients++;
87const playerData = { "ws": ws, "id": clientId, "wins": 0 }
88playerWSs.push(playerData);
89ws.onmessage = function (event) {
90message = JSON.parse(event.data);
91switch (message.type) {
92case "registration":
93const name = message.name;
94console.log("Registration: " + clientId + "/" + name);
95playerData.name = name;
96sendCurrentPlayers();
97break;
9899
case "answer":
100const answer = message;
101handleAnswer(clientId, answer);
102break;
103104
default:
105console.log("Unknown message: " + message);
106break;
107}
108};
109ws.onclose = function () {
110console.log("Player disconnected: " + clientId);
111playerWSs = playerWSs.filter(player => player.id !== clientId);
112sendCurrentPlayers();
113};
114ws.onerror = function () {
115console.log("Player error: " + clientId);
116playerWSs = playerWSs.filter(player => player.id !== clientId);
117sendCurrentPlayers();
118};
119});
120121
app.ws('/admin', function (ws, req) {
122adminWS = ws;
123sendCurrentPlayers(); // when admin registers her/himself, send current players
124ws.onmessage = function (event) {
125message = JSON.parse(event.data);
126switch (message.type) {
127case "start":
128console.log("Start game");
129sendNextQuestion();
130break;
131default:
132console.log("Unknown message: " + message);
133break;
134}
135};
136137
ws.onclose = (event) => {
138console.log("Admin disconnected");
139adminWS = null;
140sendCurrentPlayers();
141};
142143
ws.onerror = (event) => {
144console.log("Admin error: " + event);
145sendCurrentPlayers();
146};
147148
});
149150
151
const PORT = process.env.QUIZZY_PORT || 8800;
152153
var server = app.listen(PORT, function () {
154console.log(`Quizzy running at http://127.0.0.1:${PORT}/`);
155})
Client - Players
1<!DOCTYPE html>
2<html lang="en">
34
<head>
5<script>
6let ws = undefined
7try {
8ws = new WebSocket("ws://localhost:5557/player");
9} catch (e) {
10alert("WebSocket connection failed: " + e);
11document.getElementById("main").innerText = "WebSocket connection failed. Please check the server.";
12}
13ws.onmessage = (event) => {
14const data = JSON.parse(event.data);
15switch (data.type) {
16case "question":
17console.log("Question: " + data.question);
18showQuestion(data);
19break;
20case "results":
21const main = document.getElementById("main")
22main.innerText = "Results: " + event.data;
23break;
24default:
25console.log("Unknown message: " + data);
26break;
27}
28};
29ws.onclose = (event) => {
30console.log("Connection closed: " + event);
31}
32ws.onerror = (event) => {
33console.error("Error: " + event);
34}
3536
function showQuestion(data) {
37const main = document.getElementById("main")
38main.innerHTML = `<h1>Question</h1><p>${data.question}</p>`;
3940
function createAnswerButton(answer) {
41const button = document.createElement("button");
42button.innerText = answer;
43button.onclick = submitAnswer(answer);
44return button;
45}
4647
for (answer of data.answers) {
48main.appendChild(createAnswerButton(answer));
49}
50}
5152
function submitAnswer(answer) {
53return () => {
54ws.send(JSON.stringify({
55"type": "answer",
56"answer": answer
57}));
58doWait();
59}
60}
6162
function submitUsername() {
63const name = document.getElementById("username").value;
64ws.send(JSON.stringify({
65"type": "registration",
66"name": name
67}));
6869
doWait();
70}
7172
function doWait() {
73const main = document.getElementById("main");
74main.innerHTML = "Waiting for other players...";
75}
76</script>
7778
<body>
7980
<main id="main">
81<form>
82<input type="text" id="username" placeholder="Username">
83<button type="button" onclick="submitUsername();">Submit</button>
84</form>
85</main>
86</body>
8788
</html>
Client - Admin
1<!DOCTYPE html>
2<html lang="en">
34
<head>
5<script>
6const ws = new WebSocket("ws://localhost:5557/admin");
78
ws.onmessage = (event) => {
9const data = JSON.parse(event.data);
10console.log("Received: " + event.data);
11switch (data.type) {
12case "players":
13const players = document.getElementById("players")
14players.innerText =
15"["+data.players.length + " players] " +
16data.players
17.map(player => player.id + ": " + player.name)
18.join(", ");
19break;
20case "question":
21showQuestion(data);
22break;
23case "results":
24const main = document.getElementById("main")
25main.innerText = "Result: " + event.data;
26break;
27default:
28console.log("unknown: " + event.data);
29break;
30}
31};
3233
ws.onclose = (event) => {
34console.log("Connection closed: " + event);
35const main = document.getElementById("main")
36main.innerText = "Connection closed - you need to restart.";
37};
38ws.onerror = (event) => {
39console.log("Connection error: " + event);
40};
4142
function startGame() {
43ws.send(JSON.stringify({"type": "start"}));
44}
4546
function showQuestion(data) {
47document.getElementById("main").innerText = `
48question: ${data.question}; correct answer: ${data.correct}
49`
50}
51</script>
52</head>
5354
<body>
55<main id="main">
56<h1>Players</h1>
57<p id="players"></p>
58<button type="button" onclick="startGame();">Start Game</button>
59</main>
60</body>
6162
</html>
Die Implementierung dient nur dazu die grundlegenden Konzepte zu verdeutlichen. Es fehlen viele Aspekte wie z. B., Sicherheit.
Im Folgenden wird primär die Verwendung eines JWTs zur Authentifizierung von Benutzern demonstriert.
Die initiale Authentifizierung, die im folgenden Beispiel über ein per get-Request übermittelten Benutzernamen und Passwort erfolgt, ist nicht sicher. In einer realen Anwendung sollte für die initiale Authentifizierung ein sicherer Mechanismus verwendet werden. Eine Möglichkeit wäre z. B. die Verwendung von DIGEST Authentication (nicht empfohlen bzw. nur für einfachste Fälle). Sinnvoll wäre Basic Authentication in Verbindung mit HTTPS oder zum Beispiel der Einsatz von OAuth.
Server
1import express from "express";
2import fs from "fs";
3import path from "node:path";
4import { fileURLToPath } from "url";
5import jwt from "jsonwebtoken";
6import crypto from "crypto";
7import bodyParser from "body-parser";
89
const app = express();
1011
const SERVER_SECRET = crypto.randomBytes(64).toString("hex");
12const users = JSON.parse(
13fs.readFileSync(
14path.resolve(path.dirname(fileURLToPath(import.meta.url)), "users.json"),
15"utf8",
16),
17);
18console.log("Users: " + JSON.stringify(users));
1920
app.use(express.static("."));
21app.use(express.json());
22app.use(bodyParser.text());
2324
const verifyToken = (req, res, next) => {
25console.log("Headers: " + JSON.stringify(req.headers));
2627
const token = req.headers["authorization"].split(" ")[1];
28if (!token) {
29return res.status(401).json({ error: "Unauthorized" });
30}
3132
jwt.verify(token, SERVER_SECRET, (err, decoded) => {
33console.log("Decoded: " + JSON.stringify(decoded));
34if (err) {
35return res.status(401).json({ error: "Unauthorized" });
36}
37req.userIndex = decoded.userIndex;
38next();
39});
40};
4142
app.get("/admin/login", function (req, res) {
43const name = req.query.name;
44const password = req.query.password; // in a real app use hashed passwords!
4546
if (!name || !password) {
47res.status(400).send("Missing name or password");
48return;
49}
5051
let userIndex = -1;
52for (let i = 0; i < users.length; i++) {
53if (users[i].name === name && users[i].password === password) {
54userIndex = i;
55break;
56}
57}
58if (userIndex === -1) {
59res.status(401).send("Credentials invalid.");
60return;
61}
62console.log(
63"Authenticated: " + users[userIndex].name + " " + users[userIndex].password,
64);
6566
// Here, we can use the userIndex to identify the user;
67// but his only works as long as the user list is fixed.
68// In a real app use, e.g., a users's email.
69const token = jwt.sign({ userIndex: userIndex }, SERVER_SECRET, {
70expiresIn: "2h",
71});
72res.status(200).json({ token });
73});
7475
app.post("/admin/question", verifyToken, function (req, res) {
76const userIndex = req.userIndex;
77const question = req.body;
78console.log("Received question: " + question + " from user: " + users[userIndex].name);
7980
res.status(200).send("Question stored. Preliminary answer: 42.");
81});
8283
// Attention: a port like 6666 will not work on (most?) browsers
84const port = 8080;
85var server = app.listen(port, function () {
86console.log(`Running at http://127.0.0.1:${port}/`);
87});
Client (JavaScript)
1/*
2Initializes the login interface.
3*/
4document
5.getElementsByTagName("main")[0]
6.replaceChildren(document.getElementById("log-in").content.cloneNode(true));
7document.getElementById("login-dialog").showModal();
8document.getElementById("login-button").addEventListener("click", login);
910
let jwt = null; // JSON Web Token for authentication
1112
async function login() {
13const name = document.getElementById("administrator").value;
14const password = document.getElementById("password").value;
15const urlEncodedName = encodeURIComponent(name);
16const urlEncodedPassword = encodeURIComponent(password);
17const response = await fetch(
18"http://" +
19location.host +
20"/admin/login?name=" +
21urlEncodedName +
22"&password=" +
23urlEncodedPassword,
24);
25if (response.status !== 200) {
26console.error("Login failed: " + response.status);
27return;
28}
29const responseJSON = await response.json();
30jwt = responseJSON.token;
31console.log("Received JWT: " + jwt);
3233
document.getElementById("login-dialog").close();
3435
document
36.getElementsByTagName("main")[0]
37.replaceChildren(document.getElementById("logged-in").content.cloneNode(true));
38document.getElementById("enter-question-dialog").showModal();
39document.getElementById("send-question").addEventListener("click", sendQuestion);
40}
4142
async function sendQuestion() {
43const question = document.getElementById("question").value;
4445
const response = await fetch("http://" + location.host + "/admin/question", {
46method: "POST",
47headers: {
48"Content-Type": "text/plain",
49Authorization: `Bearer ${jwt}`,
50},
51body: question,
52});
53const text = await response.text();
54showAnswer(text);
55}
5657
function showAnswer(text) {
58document.getElementById("answer-dialog").showModal(false);
59document.getElementById("answer-paragraph").textContent = text;
60}
Work in progress!
Bei der Quizzy Komponenten handelt es sich um eine (ganz) einfache Client-Server basierte Komponente für Quizzes. Die Komponente besteht aus der Definition der Web Komponente und einem Server. Die Komponente und der Server kommunizieren über Web Sockets. Der Server führt in Hinblick auf Cross-origin requests keine besonderen Prüfungen durch!
1const express = require('express');
2const app = express();
34
const expressWs = require('express-ws')(app);
56
let clients = 0;
7let playerWSs = [];
89
let adminWS = null;
10let answersCount = 0;
11let correctAnswersCount = 0;
1213
app.use(express.static('.')); // required to serve static files
1415
function sendCurrentPlayers() {
16if (adminWS && playerWSs.length > 0) {
17allPlayers = playerWSs
18.filter(player => player.name)
19.map(player => { return { "id": player.id, "name": player.name } })
20console.log("Sending current players: " + JSON.stringify(allPlayers));
21adminWS.send(JSON.stringify({ "type": "players", "players": allPlayers }));
22}
23}
2425
function sendNextQuestion() {
26answersCount = 0;
27correctAnswersCount = 0;
28const question = "What is the capital of France?";
29const answers = ["Paris", "London", "Berlin", "Madrid"];
30const correct = "Paris";
3132
const nextQuestion = JSON.stringify({
33"type": "question",
34"question": question,
35"answers": ["Paris", "London", "Berlin", "Madrid"]
36})
37playerWSs.forEach(player => player.ws.send(nextQuestion));
38adminWS.send(JSON.stringify({
39"type": "question",
40"question": question,
41"answers": answers,
42"correct": correct
43}));
44}
4546
function sendResults() {
47const results = playerWSs.map(player => {
48return { "id": player.id, "name": player.name, "wins": player.wins }
49});
50const sortedResults = results.sort((a, b) => b.wins - a.wins);
51const resultsMsg = JSON.stringify({
52"type": "results",
53"results": sortedResults
54});
55playerWSs.forEach(player => player.ws.send(resultsMsg));
56adminWS.send(resultsMsg);
5758
}
5960
61
function handleAnswer(clientId, answer) {
62const correct = answer.answer === "Paris";
63const player = playerWSs.find(player => player.id === clientId);
64if (correct) {
65if (correctAnswersCount === 0) {
66player.wins++;
67}
68correctAnswersCount++;
69}
70answersCount++;
71if (answersCount === playerWSs.length) {
72// sendNextQuestion();
73sendResults();
74} else {
75adminWS.send(JSON.stringify({
76"type": "answers",
77"count": answersCount,
78"correctAnswersCount": correctAnswersCount
79}));
80}
81}
8283
84
app.ws('/player', function (ws, request) {
85const clientId = clients++;
86const playerData = { "ws": ws, "id": clientId, "wins": 0 }
87playerWSs.push(playerData);
88ws.onmessage = function (event) {
89message = JSON.parse(event.data);
90switch (message.type) {
91case "registration":
92const name = message.name;
93console.log("Registration: " + clientId + "/" + name);
94playerData.name = name;
95sendCurrentPlayers();
96break;
9798
case "answer":
99const answer = message;
100handleAnswer(clientId, answer);
101break;
102103
default:
104console.log("Unknown message: " + message);
105break;
106}
107};
108ws.onclose = function () {
109console.log("Player disconnected: " + clientId);
110playerWSs = playerWSs.filter(player => player.id !== clientId);
111sendCurrentPlayers();
112};
113ws.onerror = function () {
114console.log("Player error: " + clientId);
115playerWSs = playerWSs.filter(player => player.id !== clientId);
116sendCurrentPlayers();
117};
118});
119120
app.ws('/admin', function (ws, req) {
121adminWS = ws;
122sendCurrentPlayers(); // when admin registers her/himself, send current players
123ws.onmessage = function (event) {
124message = JSON.parse(event.data);
125switch (message.type) {
126case "start":
127console.log("Start game");
128sendNextQuestion();
129break;
130default:
131console.log("Unknown message: " + message);
132break;
133}
134};
135136
ws.onclose = (event) => {
137console.log("Admin disconnected");
138adminWS = null;
139sendCurrentPlayers();
140};
141142
ws.onerror = (event) => {
143console.log("Admin error: " + event);
144sendCurrentPlayers();
145};
146147
});
148149
150
const PORT = process.env.QUIZZY_PORT || 8800;
151152
var server = app.listen(PORT, function () {
153console.log(`Quizzy running at http://127.0.0.1:${PORT}/`);
154})
1/**
2* A small web component which enable us to integrate a small quiz
3* into a webpage.
4*/
5const quizzyStyles = new CSSStyleSheet();
6quizzyStyles.replaceSync(`
7:host {
8display: block;
9width: 100%;
10height: 10lh;
11background-color: #f0f0f0
12}
13main {
14height: 100%;
1516
.select-mode {
17height: 100%;
18display: flex;
19flex-direction: column;
20align-items: center;
21gap: 1em;
22align-content: space-around;
23justify-content: space-around;
2425
button,
26button[type="button"] {
27width: 40%;
28height: 2.5lh;
29flex-grow: 0;
30padding: 0.5em;
31font-size: inherit;
32}
33}
34}
35`);
3637
class Quizzy extends HTMLElement {
38constructor() {
39super();
4041
const shadow = this.attachShadow({ mode: "open" });
4243
shadow.adoptedStyleSheets = [quizzyStyles];
44shadow.innerHTML = `
45<main>
46<div class="select-mode">
47<button id="clientButton">Join Quizzy</button>
48<button id="adminButton">Administrate Quizzy</button>
49</div>
50</main>`;
51}
5253
connectedCallback() {
54const wsURL = this.getAttribute("ws-url");
5556
/**
57* Client logic
58*/
59this.shadowRoot
60.getElementById("clientButton")
61.addEventListener("click", () => {
62this.shadowRoot.innerHTML = `
63<main>
64<form>
65<input type="text" id="username" placeholder="Username">
66<button id="submit" type="button">Submit</button>
67</form>
68</main>`;
6970
const ws = new WebSocket(`${wsURL}/player`);
7172
ws.onmessage = (event) => {
73const data = JSON.parse(event.data);
74switch (data.type) {
75case "question":
76showQuestion(data);
77break;
78case "results":
79showResults(data);
80break;
81default:
82console.warn("unknown message", data);
83break;
84}
85};
86ws.onclose = (event) => {
87console.log("connection closed", event);
88};
89ws.onerror = (event) => {
90console.error("fatal error", event);
91};
9293
const showResults = (data) => {
94console.log("show results", data);
95const main = this.shadowRoot.querySelector("main");
96main.innerHTML = `<h1>Results</h1>`;
97for (const result of data.results) {
98main.innerHTML += `<p>${result.name}: ${result.wins}</p>`;
99}
100};
101102
const showQuestion = (data) => {
103const main = this.shadowRoot.querySelector("main");
104main.innerHTML = `<h1>Question</h1><p>${data.question}</p>`;
105106
const createAnswerButton = (answer) => {
107const button = document.createElement("button");
108button.innerText = answer;
109button.onclick = submitAnswer(answer);
110return button;
111};
112113
for (const answer of data.answers) {
114main.appendChild(createAnswerButton(answer));
115}
116};
117118
const submitAnswer = (answer) => {
119return () => {
120ws.send(
121JSON.stringify({
122type: "answer",
123answer: answer,
124}),
125);
126doWait();
127};
128};
129130
setTimeout(() => {
131this.shadowRoot
132.getElementById("submit")
133.addEventListener("click", () => {
134submitUsername();
135});
136});
137138
const submitUsername = () => {
139const name =
140this.shadowRoot.getElementById("username").value;
141ws.send(
142JSON.stringify({
143type: "registration",
144name: name,
145}),
146);
147148
doWait();
149};
150151
const doWait = () => {
152const main = this.shadowRoot.querySelector("main");
153main.innerHTML = "Waiting for other players...";
154};
155});
156157
/**
158* Admin logic
159*/
160this.shadowRoot
161.getElementById("adminButton")
162.addEventListener("click", () => {
163this.shadowRoot.innerHTML = `
164<main>
165<h1>Players</h1>
166<p id="players"></p>
167<button id="startGame" type="button">Start Game</button>
168</main>`;
169170
const ws = (this.ws = new WebSocket(`${wsURL}/admin`));
171172
ws.onmessage = (event) => {
173const data = JSON.parse(event.data);
174console.log("Received: " + event.data);
175switch (data.type) {
176case "players":
177const players =
178this.shadowRoot.getElementById("players");
179players.innerText =
180"[" +
181data.players.length +
182" players] " +
183data.players
184.map(
185(player) =>
186player.id + ": " + player.name,
187)
188.join(", ");
189break;
190case "question":
191this.shadowRoot.querySelector("main").innerText = `
192question: ${data.question}; correct answer: ${data.correct}
193`;
194break;
195case "results":
196const main = this.shadowRoot.querySelector("main");
197main.innerText = "Result: " + event.data;
198break;
199default:
200console.log("unknown: " + event.data);
201break;
202}
203};
204205
ws.onclose = (event) => {
206console.log("Connection closed: " + event);
207const main = this.shadowRoot.querySelector("main");
208main.innerText = "Connection closed - you need to restart.";
209};
210ws.onerror = (event) => {
211console.log("Connection error", event);
212};
213214
function startGame() {
215ws.send(JSON.stringify({ type: "start" }));
216}
217218
setTimeout(() => {
219this.shadowRoot
220.getElementById("startGame")
221.addEventListener("click", () => {
222startGame();
223});
224});
225});
226227
console.log("Quizzy component connected", this.wsURL);
228}
229230
disconnectedCallback() {
231console.log("Quizzy component disconnected");
232// Clean up any resources, event listeners, or connections,.... TODO
233}
234}
235236
customElements.define("ld-quizzy", Quizzy);
<script src="code/quizzy-component/quizzy.js"></script>
<div onclick="event.stopPropagation();">
<ld-quizzy ws-url="ws://localhost:5557">
<!-- When used as a real component, we have to either specify the
questions here (somehow encrypted) and send them to the server when
required, or refer to a set of questions stored on the server by
means of a parameter/attribute... -->
</ld-quizzy>
</div>