JavaScript
Dynamisch typisierte Skriptsprache zur clientseitigen Programmierung von Webanwendungen
./code-examples/1_hello-world/hello-world.js
ABER: keine Abkappselung von eingebundener Dritt-Software (z.B. Werbebanner, Like-Button)
Typ der Variable ergibt sich aus dem Ergebnis des Ausdrucks
Ohne Schlüsselwort: Kontext-Globale Variable
Mit let/const Schlüsselwort: Block-Scope Variable
Mit var Schlüsselwort: Function-Scope Variable
Automatische Typumwandlung
console.log("47" + 11); // "4711"
console.log("47" * 1 + 11); // 58
Die Typumwandlung kann sehr "unerwartete" Ausmaße annehmen: WAT
Automatische Typumwandlung für Bestimmung der Gleichheit
console.log("42" == 42); // true
Ohne automatische Typumwandlung
console.log("42" === "42"); // true
console.log("42" === 42); // false
console.log("42" !== "42"); // false
console.log("42" !== 42); // true
Verketten mit "+" Operator
console.log("Hello" + " " + "World"); // Hello World
Länge einer Zeichenkette
const s = "Hello World";
console.log(s.length); // 11
Zeichen Nummer x aus Zeichenkette s
const s = "Hello World";
console.log(s.chart(0)); // H
console.log(s[1]); // e
Ein- oder mehrzeilen Kommentare
// Hello World
/* Hello
World */
Anweisungen müssen nicht mit ";" beendet werden
console.log("Hello")
console.log("World");
Anweisungsblöcke werden mit geschweiften Klammern gebildet {}
for-Schleifen
for (Initialisierung; Bedingung; Befehlsfolge) {
// Anweisungen
}
for (e in collection) {
// Anweisungen
}
do-while-Schleifen
do {
// Anweisungen
} while (Bedingung)
while (Bedingung) {
// Anweisungen
}
if (Bedingung){
//Anweisungsblock
} else if (Bedingung) {
//Anweisungsblock
} else {
//Anweisungsblock
}
switch (Ausdruck){
case Wert1:
//Programmblock
break
case Wert2:
//Programmblock
break
default:
//Programmblock
}
Deklaration (mit Initialisierung)
let a = new Array();
let b = new Array(5);
let c = [];
let d = new Array("eins", "zwei", "drei");
let e = [1, 2, 3, 4]
Länge
console.log(a.length) // 0
console.log(b.length) // 5
Zugriff
a[4] = "Hello World";
let s = a[4];
Deklaration
function Funktionsname(Parameter1, Parameter2) {
// Programmblock;
return Wert;
}
Anzahl der Parameter beim Aufruf ist beliebig!
Ungesetzte Parameter sind
"undefined"
Ohne oder leeres return ergibt implizit "undefined"
function f(Parameter1, Parameter2) {
console.log(Parameter1);
console.log(Parameter2);
return Parameter1;
}
f("eins", "zwei");
// eins
// zwei
// return: "eins"
f("drei");
// drei
// undefined
// return: "drei"
Deklaration einer Funktion muss
erfolgen.
Hilfskonstrukt!
Geht sehr schnell auf die Les- und
Wartbarkeit
function g() {
console.log(g.arguments[0]);
console.log(g.arguments[1]);
}
g(1, 2);
// 1
// 2
g("drei");
// drei
// undefined
let a = 0;
let b = 0;
function test() {
a++; // Zugriff auf Variable a
console.log(a);
let b = 1; // Neue(!) lokale Variable
console.log(b);
}
test();
// 1
// 1
console.log(b); // 0
Ohne Schlüsselwort wird eine globale Variable erstellt bzw. überschrieben!
"Window trashing" -> schlechter Stil
function Calculator(x, y) { // Deklaration
this.x = x;
this.y = y;
this.sum = function sum() {
return this.x + this.y;
}
}
const c = new Calculator(4,3); // Instanziierung
c.sum(); // 7, Methodenaufruf
Objekte sind zur Laufzeit erweiterbar
const c = new Calculator(4,3); // Instanziierung
c.diff = function diff() {
return this.x - this.y;
};
c.diff() // 1, Methodenaufruf
const c = { // Instanziierung
x: 4,
y: 3,
sum: function() {
return this.x + this.y;
}
}
c.sum(); // 7, Methodenaufruf
function Position(x,y) {
this.x = x;
this.y = y;
return "(" + x + "," + y + ")"
}
a = Position(3,4); // "(3,4)"
a = new Position(3,4); // Objekt mit x=3 und y=4
Bezugspunkt fuer den Aufrufer/Kontext
Bisher: die aktuelle Objekt-Instanz
function whatTheThis() {
return this.a;
}
a = 16;
console.log(a); // 16
console.log(window.a); //16
console.log(whatTheThis()); // 16
console.log(window.whatTheThis()); // 16
Außerhalb aller Anweisungsblöcke (global execution context) zeigt es auf das root-Objekt,
im
Browser: window
Modernes JavaScript: "globalThis" verweisst immer auf das root-Objekt
Representation des HTML-Dokuments im Speicher
Datenstruktur: Baum (Tree Powers Activate!)
Schnittstelle zwischen
JavaScript und HTML-Dokument
Einstiegsschnittstelle: document
Suchen eines Elements
// Modern: CSS-Selektor
// Findet erstes 'h1'-Element, <-Objekt
document.querySelector("h1");
// Findet alle 'h1'-Elemente, <-Array
document.querySelectorAll("h1");
// Klassisch: einzelne Methoden
// Findet Element mit id='id', <-Objekt
document.getElementById("id");
// Findet alle Elemente mit name='name', <-Array
document.getElementsByName("name");
// Findet alle 'h1'-Elemente, <-Array
document.getElementsByTagName("h1");
// ...
Erzeugen eines neuen Elements
// Erzeugt neues "div"-Element, <-Objekt
document.createElement("div");
// Erzeugt neuen Textinhalt, <-Objekt
document.createTextNode("Lorem ipsum");
Passiert alles im Speicher! Erst Sichtbar wenn es dem "body" hinzugefügt wird!
Manipulieren des Dokuments
// Hängt ein Element als Kind eines anderen in den Baum
element.appendChild(element);
// !Wirft HierarchyRequestError (DOMException)
// z.b. wenn ein Zyklus entstehen würde
// Objekte erstellen
const newDiv = document.createElement("div");
const newContent = document.createTextNode("text!");
// Füge den Inhalt dem Div hinzu
newDiv.appendChild(newContent);
// Füge den neuen Div dem Body hinzu
document.body.appendChild(newDiv);
Manipulieren des Dokuments
// Ersetzt ein Element durch ein anderes
element.replaceChild(newElement, oldElement);
// Entfernt ein Element
element.replaceChild(oldElement);
// Liest Attribut eines Elements aus
element.getAttribute("name");
// Setzt ein Attribut des Elements
element.setAttribute("name", "value");
Navigation durch das Dokument
// Zeiger auf Kind-Elemente
element.firstElementChild; element.lastElementChild;
// Zeiger auf "Beziehungs"-Elemente
element.nextElementSibling; element.previousElementSibling;
node.parentNode;
Element/Knoten-Attribute
// Textinhalt - komplett
node.textContent;
// Textinhalt:
// - human-readable: kein script, style
// - style-aware: kein Text von "hidden" Elements
element.innerText;
// HTML-Inhalt
element.innerHTML;
// !Wirft SyntaxError (DOMException)
// wenn zugewiesener Wert kein gültiges HTML ist
// Style
element.style;
// Setze Schriftfarbe
element.style.color = 'red';
// Standardfarbe wiederherstellen
element.style.color = null;
Beispiel: <p onclick="alert('click')">Hallo!</p>
element.addEventListener(event, listener, options)//new(er)
element.addEventListener(event, listener, useCapture)//old(er)
element.removeEventListener(event, listener, options)
element.removeEventListener(event, listener, useCapture)
const listener = function(event /* MouseEvent */) {
console.log(event.clientX + " / " + event.clientY);
};
element.addEventListener("click", listener);
Übergeben von weiteren Parametern?
const listener = function(event, p1, p2) {
console.log(p1 + "; " + p2);
};
element.addEventListener("click", function(event) {
listener(event, p1, p2);
});
oder via Handlerzuweisung per Attribut
<p onclick="listener(event, p1, p2);">...</p>
// indiziert
document.forms[3].elements[4].value
// assoziativ via names
document.forms.myForm.elements.x.value
// assoziativ verkürzt
document.myForm.x.value
Empfehlung: Eindeutige Namen für Formulare (und Eingabeelemente) vergeben!
Empfehlung: Zugriffsformen nicht mischen!!
(Les- und Wartbarkeit)
let a = 1;
b = 5;
let c = new Array("eins", "zwei", "drei");
function test(e, f) {
for(g in c) {
let b = e + a;
a = a + 1;
}
c = [f];
}
console.log("a='" + a + "' b='" + b + "' c='" + c + "'");
test(4,5);
console.log("a='" + a + "' b='" + b + "' c='" + c + "'");
test(6,7);
console.log("a='" + a + "' b='" + b + "' c='" + c + "'");
Welchen Wert haben die Variablen a,b,c vor und nach den "test"-Aufrufen? Welche Strings geben die "console.log" aus?
a='1' b='5' c='eins,zwei,drei'
a='4' b='5' c='5'
a='5' b='5' c='7'
Asynchrone Datenübertragung zwischen
Browser und Server
Daten zum Browser übertragen außerhalb von Seiten-Navigationen
Aufruf des Servers, Empfangen und Verarbeiten der Antwort durch JavaScript
Daten müssen nicht(!) im XML-Format sein
Seitenabruf
AJAX
Vorteile
Nachteile
API-Stile, Protokolle
https://api.github.com/users/chr1shaefn3r
{
"login": "chr1shaefn3r",
"id": 11996484,
"node_id": "MDQ6VXNlcjExOTk2NDg0",
"avatar_url": "https://avatars.githubusercontent.com/u/11996484?v=4",
"type": "User",
"name": "Christoph Häfner"
}
chr1shaefn3r
https://avatars.githubusercontent.com/u/11996484?v=4
Christoph Häfner
Herstellen einer Verbindung (MDN)
const request = new XMLHttpRequest();
request.open(method, url[, async=true[, user=null[, password=null]]])
Anfrage senden (MDN)
request.send([body=null])
Daten der Antwort: responseText, responseXML
// XMLHTTPRequest erzeugen
const request = new XMLHttpRequest();
// Event-Handler um Antwort auszugeben
function reqListener () {
console.log(this.responseText);
}
// Event-Handler für Antwort setzen
request.addEventListener("load", reqListener);
// Verbindung öffnen
request.open("GET", "https://api.github.com/users/chr1shaefn3r");
// Anfrage abschicken
request.send();
// XMLHTTPRequest erzeugen
const request = new XMLHttpRequest();
// Event-Handler für Antwort setzen
request.onreadystatechange = function receive() {
if(request.readyState==4) {
console.log(request.responseText);
}
}
// Verbindung öffnen
request.open("GET", "https://api.github.com/users/chr1shaefn3r");
// Anfrage abschicken
request.send();
const responsePromise = fetch(resource, [, init])
Rückgabewert der Fetch-API (MDN)
fetch(
"https://api.github.com/users/chr1shaefn3r"
).then(
function(response) {
response.json().then(
function(githubUser) {
console.log(githubUser);
}
);
}
);
fetch("https://api.github.com/users/chr1shaefn3r")
.then((response) => response.json())
.then((githubUser) => console.log(githubUser));
https://api.github.com/users/chr1shaefn3r
{
"login": "chr1shaefn3r",
"id": 11996484,
"node_id": "MDQ6VXNlcjExOTk2NDg0",
"avatar_url": "https://avatars.githubusercontent.com/u/11996484?v=4",
"type": "User",
"site_admin": false,
"name": "Christoph Häfner",
"hireable": null,
"bio": null,
"public_repos": 5,
}
JSON.parse
const myObject = JSON.parse('{"label": "Hello World"}');
console.log(myObject.label); // "Hello World"
const myObject = JSON.parse(
'{"label": function() { return "Hello World" }'
);
// Uncaught SyntaxError: Unexpected token [...]
JSON.stringify
const myObject = {"label": "Hello World"};
console.log(JSON.stringify(myObject));
// '{"label":"Hello World"}'
JSON.stringify(objectWithCircularReference);
//Uncaught TypeError: Converting circular structure to JSON
Tiefes Klonen eines JavaScript-Objekts
const mutate = function(o) {
o.a = 4;
}
const myObject = { a: 2 };
mutate(myObject);
console.log(myObject.a); // 4
const myObject = { a: 2 };
mutate(JSON.parse(JSON.stringify(myObject)));
console.log(myObject.a); // 2
Moderne alternative: structuredClone() (MDN)
Kann
auch mit zyklischen Abhängigkeiten umgehen
Website/HTML:
<body>
<h1>Fetch Beispiel</h1>
<p>x = <span id="x"></span></p>
<p>t = <span id="t"></span></p>
<p>
<input type="button" value="load" onclick="loadData()">
</p>
</body>
JSON API-Antwort:
{ "x": 5, "t": "hello World" }
JS-Script:
function loadData () {
fetch(url)
.then(response => response.json())
.then(function(data) {
document.getElementById("x").innerText = data.x;
document.getElementById("t").innerText = data.t;
});
}
Old-School JavaScript:
function und
this-Kontext wird vom Aufrufer gesetzt
Überraschender
this-Kontext, verkompliziert Einstieg
in JavaScript
Modernes JavaScript:
=> und
this-Kontext des Scopes
Leichter verständlich, weniger
Workarounds
this ist nicht was man erwarten würde
function GithubUser(name) {
this.name = name
};
GithubUser.prototype.download = function() {
console.log(this.name);
fetch("https://api.github.com/users/" + this.name)
.then(function(data) {
console.log(this.name);
});
}
new GithubUser("chr1shaefn3r").download();
// "chr1shaefn3r" (Zeile 6)
// undefined (Zeile 9)
Warum ist "this.name" in Zeile 6 + 9 nicht das gleiche? WTF?!?
Workaround um an this ranzukommen
function GithubUser(name) {
this.name = name
};
GithubUser.prototype.download = function() {
var that = this;
fetch("https://api.github.com/users/" + that.name)
.then(function(data) {
console.log(that.name);
});
}
new GithubUser("chr1shaefn3r").download();
// "chr1shaefn3r" (Zeile 9)
function GithubUser(name) {
this.name = name
};
GithubUser.prototype.download = function() {
fetch("https://api.github.com/users/" + this.name)
.then((data) => {
console.log(this.name);
});
}
new GithubUser("chr1shaefn3r").download();
// "chr1shaefn3r" (Zeile 8)
weniger Syntax zu tippen, richtiges Ergebnis, ganz ohne Workaround: great success!
Old-School JavaScript:
var und
Function-Scope
Überraschende Variablengültigkeiten, verkompliziert
Einstieg in JavaScript
Modernes JavaScript:
let/const und Block-Scope
Vergleichbarer mit bestehenden
Konzepten aus anderen Programmiersprachen (Java, C++, Rust, etc.)
function whatTheFunctionScope(data) {
for(var index = 0; index < data.length; index++) {
/* do something with data[index] */
}
console.log(index);
};
whatTheFunctionScope(["wert"]); // 1
Warum ist die Variable "index" in Zeile 5 noch gültig?!? WTF?!?!
function whatTheFunctionScope() {
console.log(index);
var index = 1;
console.log(index);
};
whatTheFunctionScope();
// undefined
// 1
Warum ist die Variable "index" in Zeile 2 schon verfügbar?!? WTF?!?!
function theBlockScope(data) {
for(let index = 0; index < data.length; index++) {
/* do something with data[index] */
}
console.log(index);
};
theBlockScope(["wert"]);
// Uncaught ReferenceError: index is not defined
Die Variable "index" ist auf den Block der for-Schleife beschränkt. So wie man es erwarten würde.
KEIN hoisting!
Was bisher geschah:
Was noch fehlt:
Und woher kommt eigentlich dieses "toString"?:
const myObject = { x: 4, y: 2};
console.log(myObject.toString()); // "[object Object]"
Prototyp erweitern
const obj = { x: 4, y: 2};
obj.__proto__.d = 5; // __proto__ ist Implementierungsdetail
Object.getPrototypeOf(obj).d = 5; // ES5 Standard
console.log(obj.d); // 5
Klassen-Prototype erweitern
function Calculator(x, y) {
this.x = x;
this.y = y;
}
Calculator.prototype.sum = function() {
return this.x + this.y;
}
const c = new Calculator(4, 2);
console.log(c.sum()); // 6
function Person(name) {
this.name = name;
}
Person.prototype.vorstellen = function() {
console.log("Hallo, mein Name ist " + this.name)
}
function Dozent(name, fach) {
Person.call(this, name);
this.fach = fach;
}
// Prototypen-Kette setzen "Vererbung"
Dozent.prototype = Person.prototype;
Dozent.prototype.bewerten = function (klausur) {
if(klausur.fach === this.fach) { /* Impl */ }
}
new Dozent("Christoph", "WebEngineering1").vorstellen();
// "Hallo, mein Name ist Christoph"
Prototypes
ES6: classes
class Person {
name;
constructor(name) {
this.name = name;
}
vorstellen() {
console.log("Hallo, mein Name ist " + this.name)
}
}
class Dozent extends Person {
fach;
constructor(name, fach) {
super(name);
this.fach = fach;
}
bewerten(klausur) {
if(klausur.fach === this.fach) { /* Impl */ }
}
}
new Dozent("Christoph", "WebEngineering1").vorstellen();
// "Hallo, mein Name ist Christoph"
Typescript: Verbreitetste JS-Variation [0]
Erweitert
JS-Syntax um Typsystem (Superset), kompiliert nach JavaScript
ASM.js -> WASM (WebAssembly)
Anstatt JS programmieren, Web-Plattform als
"compile target",
z.B. LibreOffice im Browser [1],
[2]
Nach REST: GraphQL
Abfragesprache, mit einem Request aus vielen
verschiedenen Resourcen
genau die Attribute abfragen die im UI benötigt werden, z.B. Github-API v4
HTML-Vorlage: website-aufgabe3.html
Github Profil Seite
Github User REST-Api: "https://api.github.com/users/{username}"