Javascript: Eine Skriptsprache mit dynamischer Typisierung
- Sowohl “Skriptsprache” als auch “dynamische Typisierung” sind sehr schwammige Begriffe
- Unterschiedliche Ausprägungen je nach konkret vorliegender Sprache
- Skriptsprache
-
- Abhängigkeit von einer Laufzeitumgebung (Runtime, Virtual Machine)
- Ursprüngliche Umgebung: Browser
- Mittlerweile auch typisch: Server
- Keine Vorab-Kompilierung in Maschinencode (aber möglicherweise JIT-Kompilierung)
- Dynamische Typisierung
-
- Typüberprüfungen werden erst zur Laufzeit vorgenommen
- Typen können zur Laufzeit erweitert werden
ECMA-Script?
- Standard 262 bei der European Computer Manufacturers Association
- “JavaScript” ist streng genommen nur der Name der Implementierung von Netscape / Mozilla
- “JavaScript” ist ein werbewirksamer Name
- Syntax ein bisschen ähnlich zu Java, ansonsten kaum Gemeinsamkeiten
Zurückhaltende Buchempfehlung
- “JavaScript: The Good Parts”, von Douglas Crockford
- Mittlerweile leider etwas veraltet, aber ein guter Einstieg für Leute die Erfahrung mit anderen Programmiersprachen haben
JavaScript-Umgebungen
- Wichtige Unterscheidung: Die Programmiersprache JavaScript kann in den unterschiedlichsten Umgebungen laufen
-
- Auf einem Server (Zugriff auf Dateisystem, Datenbanken, …)
- Im Browser (Zugriff auf
DOM
-Baum, Ortungsdienste, Webcam, …)
- Als Teil einer Smartphone-App (Wie Browser, zusätzlich: Benutzerkennung, Dateisystem, …)
- Als Teil einer beliebigen Desktop-Anwendung
- In allen Fällen: Identische Sprache, andere verfügbare Objekte und Funktionen
- Ein Server hat keine Webcam, ein Browser keinen Dateisystemzugriff, …
- In den meisten Fällen: Kommunikation zwischen Umgebungen erfolgt über spezielle Protokolle
-
- Nicht möglich: “Mal eben so” eine Funktion auf dem Server aufrufen (“Da ist doch auch JavaScript”)
- Für Browser und Server typischerweise
AJAX
(wird später behandelt)
In dieser Veranstaltung: Die Programmiersprache JavaScript. Die spezifischen Laufzeitumgebungen für Server und Client sind Teil separater Vorlesungsteile.
Grundlegende Syntax
- Geschweifte Klammern (
{ }
) für Blöcke (z.B. bei Schleifen oder Verzweigungen)
-
- Wie in C, C++, C#, Java …
- Anders als in Pascal & Visual Basic
- Ausdrücke mit Infix-Notation (
(1 + 2) / 5
)
- Wie in C, C++, C#, Java, Pascal, Visual Basic …
- Semikolon (
;
) zur Abgrenzung von Anweisungen
- Wie in C, C++, C#, Java, Pascal …
- Funktionsaufrufe mit runden Klammern (
roundNumber(1.5123, 3)
)
- Wie in C, C++, C#, Java, Pascal, Visual Basic …
- Kein explizites Hauptprogramm
- Code wird sofort ausgeführt, wenn er evaluiert wird
Datentypen und Notation von Konstanten
- Grundsätzlich wie
JSON
- Siehe Vorlesungsunterlagen
- Allerdings ist Javascript etwas weniger strikt:
-
- Schlüssel für Objekte müssen nicht zwingend in Anführungszeichen gesetzt werden
- Abschließende Kommata in Listen und Objekten sind erlaubt
- Als Anführungszeichen für Strings sind auch Paare von einfachen Hochkommata (
'
) erlaubt
Ausgabe von Daten
- Aufgrund der unterschiedlichen Umgebungen: Kein typischer Kanal wie
stdout
verfügbar
- Konvention: globales Objekt
console
verfügbar
console.log()
& console.info()
für allgemeine Logausgaben
console.warn()
für Warnungen
console.error()
für Fehler
- Komplexe Datentypen werden (in der Regel) sinnvoll ausgegeben
-
- Voraussetzung: Übergabe als einzelne Parameter
- Typischerweise
JSON
, je nach Umgebung allerdings syntaktisch häufig inkorrekt
- Im Browser: Komplexe Darstellung
-
console.log("Hello World");
console.log({
"hello": "world"
});
console.log([
"hello",
"world"
]);
console.log([1,2],[3,4]);
Mathematische Ausdrücke
- Werden in Infix-Schreibweise notiert
- Wie in C, C++, C#, Java …
- Typische mathematische Operationen, allerdings immer mit Fließkommaarithmetik
- Nur ein Datentyp
number
für Zahlen
- Inhärent ganzzahlige Operationen konvertieren zu und anschließend von Ganzzahlen
- Restklassendivision und Bit-Operationen
String-Ausdrücke
- Nutzen den
+
-Operator zur Konkatenation
- Wenn eine Seite ein String ist, wird die andere Seite ebenfalls als String interpretiert
Gemischte Ausdrücke
- JavaScript wird versuchen eine passende Typkombination zu raten
- Häufigster (und gewünschter) Fall: Ein String der sich sinnvoll als Zahl interpretieren lässt
- Häufig taucht der Wert
NaN
(Not a Number) auf
- Dieser Wert zieht sich dann durch alle weiteren Zahlenoperationen
Boolesche Ausdrücke: Logische Operatoren
- Typische logische Operatoren:
!
(Negation), &&
(logisches “Und”) und ||
(logisches “Oder”)
- Funktionieren für Paare mit identischen Typen erwartungskonform
Boolesche Ausdrücke: Vergleichsoperatoren
- Typische Vergleichsoperatoren:
==
, !=
, >
, >=
, <
und <=
- Funktionieren für Paare mit booleschen Typen erwartungskonform
Strikte Vergleichsoperatoren
- Problematisch: Einfache Vergleiche nehmen komplizierte Konvertierungen vor, um nach Möglichkeit
true
zu ergeben
- Nur ein Problem bei unterschiedlichen Typen auf der linken und der rechten Seite
- Daher: Strikte Vergleichsoperatoren:
===
und !==
- Schlagen bei unterschiedlichen Typen fehl, alle obigen Vergleiche ergäben so
false
Nicht transitive Vergleiche, nicht kommutative Operatoren und teils fragwürdige automatische Konvertierungen
- (Fast) unendliche Quelle für eine Vielzahl (nerdiger) Witze
-
Verzweigungen
- Auswertung eines booleschen Ausdrucks (
if
), Alternativen mit else
- Wie in C, C++, C#, Java, (Visual Basic), Pascal, …
- Neben
false
noch “falsey”-Werte
-
0
, -0
und NaN
- Leere Strings (
""
und ''
)
null
und undefined
- Im
DOM
: document.all
(leider kein Scherz)
Vier Zwei Arten von Variablendeklarationen
- Die Möglichkeiten:
-
let
zur Einführung ab der Erwähnung
const
zur Einführung von konstanten Werten ab der Erwähnung
- Historisch:
var
zur Einführung am Beginn des Funktions-Blocks
- Ohne Schlüsselwort: Implizit im globalen Namensraum
- Globale Zuweisungen und Verwendung von
var
nur historisch relevant
- Alle modernen Umgebungen unterstützen
let
(caniuse) und const
(caniuse)
- Für alle Varianten gilt: Angabe eines Typs nicht möglich
- Es gilt immer der Typ der letzten Zuweisung
- Zulässige Bezeichner beginnen mit Buchstaben,
_
oder $
, danach auch Ziffern
- Groß- und Kleinschreibung ist relevant
Sinnvoll: Einführung von lokalen Variablen mit let
- Gültigkeit beginnt ab der Zeile der Deklaration
- Wie in C, C++, C#, Java …
- Nicht möglich: Angabe eines Typs
-
- Jede Variable hat implizit den Typ des zuletzt zugewiesenen Wertes
- Typen einer Variable können sich folglich zur Laufzeit verändern
-
Hello World
[ 'Hello', 'World' ]
2
[stdin]:11
console.log(other);
^
ReferenceError: other is not defined
at [stdin]:11:13
Sinnvoll: Einführung von lokalen Konstanten mit const
- Gültigkeit beginnt ab der Zeile der Deklaration
- Wie in C, C++, C#, Java …
- Im Unterschied zu
let
: Nur einmalige Zuweisung möglich
- (Sehr schwache 😐) Garantie, dass sich der Typ nicht verändert
- Vorsicht: “Konstantheit” ist nicht transitiv
-
- Lediglich der Verweis auf die Variable selbst darf nicht verändert werden, eine Neuzuweisung an den Bezeichner ist also nicht möglich
- Folge: Die Werte “in” Arrays und Objekten können sich noch verändern
- Für Informatiker: Wie
final
in Java, nicht wie const
in C++
Historisch: Einführung von Variablen mit var
- Gültigkeit beginnt dann ab dem jeweiligen Funktionsblock, Zuweisungen werden erst in der “korrekten” Zeile vorgenommen
- Bis zur ersten Verwendung ist der Wert
undefined
if
, for
, while
, … bilden keinen Block im Sinne der Deklaration mit var
- Deklaration der Variable wird vor den Block gezogen
Selten nötig, zu Testzwecken aber praktisch: Aktuelle Typen erfragen mit typeof
- Operator
typeof
berechnet einen String der den Typ des übergebenen Ausdrucks angibt
-
- Mögliche Resultate sind:
undefined
, object
, boolean
, number
, string
, symbol
, function
- Vorsicht: Es gibt kein Resultat
array
, der Typ eines Arrays ist object
-
let myVar = 1;
console.log("#1", typeof(myVar), myVar);
myVar = myVar + "4";
console.log("#2", typeof(myVar), myVar);
myVar = myVar / "7";
console.log("#3", typeof(myVar), myVar);
myVar = [myVar, myVar];
console.log("#4", typeof(myVar), myVar);
myVar = myVar[2];
console.log("#5", typeof(myVar), myVar);
Klassische Schleifen
for
, while
und do while
- Wie in C, C++, C#, Java, Pascal, Visual Basic …
- Bilden keinen Block im Sinne der Deklaration mit
var
- Deklaration der Variable wird vorgezogen
- Gefährlich: Nutzung von
for .. in
- Im Rahmen dieser Veranstaltung: Einfach vermeiden
Zugriff auf Arrays
JavaScript-Arrays verhalten sich nicht wie "klassische" Arrays aus Visual Basic, Pascal, Java, C, ... Stattdessen können Arrays (wie Listen) auch nach dem Anlegen noch erweitert werden.
- Klassischer Indexzugriff über den
[]
-Operator, gezählt wird von 0
an
- Wie in C, C++, C#, Java …
- Zuweisungen an Schlüssel zu weit “hinten” erweitern das Array
-
length
Eigenschaft wird entsprechend angepasst
- Unbekannte Werte sind
undefined
Hinten anhängen und löschen
- Intuitiv:
push
hängt neue Elemente an das Ende der Liste an
-
- Verändert das Array auf dem es aufgerufen wird
- Ergebnis des Aufrufs ist die neue Länge des Arrays
- Intuitiv:
pop
entfernt Elemente vom Ende der Liste
-
- Verändert das Array auf dem es aufgerufen wird
- Ergebnis des Aufrufs ist das vormals letzte Element
-
const bigIdeas = [
"Rollercoaster", "Swinter", "X-Ray-Glasses"
]
console.log("Initial", bigIdeas);
const pushResult = bigIdeas.push("Platypus Restaurant");
console.log("push() result:", pushResult);
console.log("After push :", bigIdeas);
const popResult = bigIdeas.pop();
console.log("pop() result :", popResult);
console.log("After append :", bigIdeas);
-
Initial [ 'Rollercoaster', 'Swinter', 'X-Ray-Glasses' ]
push() result: 4
After push : [ 'Rollercoaster',
'Swinter',
'X-Ray-Glasses',
'Platypus Restaurant' ]
pop() result : Platypus Restaurant
After append : [ 'Rollercoaster', 'Swinter', 'X-Ray-Glasses' ]
In der Mitte anhängen und löschen
- Weniger intuitiv:
splice
kann an einer bestimmten Stelle sowohl einfügen als auch löschen
-
- Signatur:
splice(index, deleteCount, items...)
- Rückgabewert: Array der gelöschten Elemente
-
const bigIdeas = [
"Rollercoaster", "Swinter", "X-Ray-Glasses"
]
console.log("bigIdeas = ", bigIdeas);
console.log("bigIdeas.splice(0, 1)");
console.log("return :", bigIdeas.splice(0, 1));
console.log("new value:", bigIdeas);
console.log("bigIdeas.splice(1, 0, 'Time machine')");
console.log("return :", bigIdeas.splice(1, 0, 'Time machine'));
console.log("new value:", bigIdeas);
console.log("bigIdeas.splice(1, 2, 'Rocket')");
console.log("return :", bigIdeas.splice(1, 2, 'Rocket'));
console.log("new value:", bigIdeas);
-
bigIdeas = [ 'Rollercoaster', 'Swinter', 'X-Ray-Glasses' ]
bigIdeas.splice(0, 1)
return : [ 'Rollercoaster' ]
new value: [ 'Swinter', 'X-Ray-Glasses' ]
bigIdeas.splice(1, 0, 'Time machine')
return : []
new value: [ 'Swinter', 'Time machine', 'X-Ray-Glasses' ]
bigIdeas.splice(1, 2, 'Rocket')
return : [ 'Time machine', 'X-Ray-Glasses' ]
new value: [ 'Swinter', 'Rocket' ]
Zugriff auf alle Schlüssel und Werte eines Objekts
- Aufzählen von Schlüssen oder Werten mit Funktionen aus
Object
-
Object.keys
liefert ein Array aller Schlüssel des übergebenen Objektes
Object.values
liefert ein Array aller Werte des übergebenen Objektes
Ein Objekt könnte beliebige Schlüssel haben, auch keys
und values
. Daher müssen, anders als bei Arrays, spezielle Objekt-"Methoden" als Funktionen des "Object
-Objektes" implementiert werden.
-
[ 'phineas', 'ferb', 'doofenschmirtz' ]
[ 'Maple Drive 2308',
'Maple Drive 2308',
'Doofenshmirtz Evil Inc.' ]
Zugriff auf bestimmte Werte eines Objekts
- Wie bei Arrays:
[]
-Operator mit Angabe eines Schlüssels
- Typ des Schlüssels ist immer
string
- Zusätzlich zum
[]
-Operator: Punktnotation
- Wie in C-
struct
, Java-Klassen, Pascal record
, Visual Basic Structure
, …
- Einfügen von neuen Schlüsseln per Zuweisung
- Wie bei
Map
in Java, std::map
in C++, …
-
const homes = {
"phineas": "Maple Drive 2308",
"ferb": "Maple Drive 2308",
"doofenschmirtz": "Doofenshmirtz Evil Inc."
}
homes.isabella = "across from the Flynn-Fletcher house";
homes["Major Monogram"] = "O.W.C.A. Secret Headquarters";
const keys = Object.keys(homes);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
console.log(key, "=>", homes[key]);
}
Löschen aus Objekten
- Operator
delete
zum Löschen von Einträgen
-
- Technisch gesehen: Angabe des zu löschenden Schlüssels
- Syntaktisch gesehen: Angabes des zu löschenden Wertes im Objekt
Template-Literals
- Seit ECMA-Script 6: Einbettung von Ausdrücken in Strings
- Praktische abkürzende Schreibweise ohne möglicherweise aus Versehen auf das mathematische
+
zurückzufallen
- Syntax: Notation des Strings in “backticks” (französischer accent grave):
`Template-String`
-
- Erlaubt Zeilenumbrüche in Strings, Leerzeichen werden dabei aber exakt wiedergegeben
- Andere Anführungszeichen können ebenfalls genutzt werden
- Syntax: Notation von Ausdrücken in Template-Strings mit
`${ expr }`
- Ausdruck hat Zugriff auf gesamte Umgebung
Can I Use template-literals? Data on support for the template-literals feature across the major browsers from caniuse.com.
Die Standardbibliothek
- JavaScript kommt mit einer recht typischen Standardbibliothek
- Zusätzlich Erweiterungen für Browser oder Server verfügbar
- Einige Inhalte werden hier nicht explizit besprochen, könnten aber hilfreich sein:
-
- Die
Math
-Bibliothek mit Konstanten wie Math.PI
oder Funktionen wie Math.max
, Math.pow
, Math.random
, Math.sin
, …
- String-“Funktionen” sind Methoden des
String
-Objekts und haben typische Funktionalität wie startsWith
, substr
, trim
, toUpperCase
, …
- Datums-“Funktionen” sind Methoden des
Date
-Objekts und haben typische Funktionalität wie getHours
, getMonth
, … Vorsicht: Benutzen Sie nicht getYear
, sondern stattdessen getFullYear
Umgang mit JSON
-Daten
- Zwei wesentliche Funktionen:
-
JSON.parse
wandelt einen String in die entsprechende Datenstruktur
JSON.stringify
wandelt eine Datenstruktur in die entsprechende JSON
-Stringdarstellung
-
const data = JSON.parse('{ "name": "Phineas" }');
console.log("Parsed Name:", data.name);
console.log("Parsed Data:", data);
const dataString = JSON.stringify(data);
console.log("Stringified:", dataString);
Funktionen
- Nehmen Parameter entgegen, berechnen Werte und bilden einen Block
- Wie in C, C++, C#, Java, Pascal, Visual Basic …
- Syntax zur Deklaration:
function name(param1, ...)
- Wie bei Variablen: Keine Typangaben möglich
- Ähnlich wie
var
: Deklaration und Implementierung wird vorgezogen
- Funktionen können also vor ihrer Deklaration Quelltext verwendet werden
Nicht auf diese Umsortierung verlassen! Hohes Maß an Fehleranfälligkeit, wenn die Umsortierung durch den Compiler eine Rolle spielt.
Ergebnisse von Funktionen
- Wie bei Parametern & Variablen: Keine Typangaben
-
- Eine Funktion kann jederzeit jeden Typ berechnen
- Wenn eine Funktion nichts berechnet ist der Wert
undefined
- Schlüsselwort
return
beendet einen Funktionsuafruf und gibt einen Wert zurück
-
- Wie in C, C++, C#, Java
- In Pascal & Visual Basic: Definition des Rückgabewerts über Zuweisung an den Funktionsnamen
Anonyme Funktionen / Funktionen höherer Ordnung
- Funktionen müssen keinen Namen haben und können wie “normale” Daten übergeben werden
- Ergo: Zuweisung an Variablen möglich
- Syntax zur Deklaration anonymer Funktionen:
function (param1, ...)
- Wie bei Variablen: Keine Typangaben möglich
- An dieser Stelle hilfreich: Deklaration mit
const
- Hinterlegte Funktion kann dann nicht mehr überschrieben werden
Gewöhnen Sie sich bitte diese Schreibweise an, Sie werden im Verlauf der Veranstaltung noch an vielen Stellen Funktionen wie Variablen behandeln.
Primitive Typen und Referenztypen
- Primitive Typen verhalten sich wie Werte
- Wie in C# oder Java
- Primitive Werte sind:
-
true
und false
, also boolean
null
und undefined
, also “fehlende” Werte
string
, also Zeichenketten
number
, also Zahlen
symbol
(Teil der fortgeschrittenen Veranstaltung)
- Alle anderen Typen (konkret:
object
) verhalten sich wie Referenzen
- Wie in C# oder Java
Beispiel zu Parameterübergabe
-
function add1(num) {
num++;
}
function append1(arr) {
arr.push(1);
}
function change1(obj) {
obj.one = "changed";
}
const myNum = 0;
console.log("number before add1: ", myNum);
add1(myNum);
console.log("number after add1: ", myNum);
const myArray = [0];
console.log("Array before append1:", myArray);
append1(myArray);
console.log("Array after append1:", myArray);
const myObj = { "one": "inital" };
console.log("Object before change1:", myObj);
change1(myObj);
console.log("Object after change1:", myObj);
Defensiv programmieren mit typeof
- Grundidee: Das Programm lieber krachend beenden als möglicherweise etwas falsches zu tun
- Werfen einer Ausnahme als einfachster “Notausstieg”
- Problem: Funktioniert nur gut für primitive Typen
- Komplexe Typen haben den
typeof
-Wert object
und lassen sich nicht gut inspizieren
- Problem: Erfordert eine ganze Menge “langweiligen” Code …
- … der im produktiven Betrieb niemals fehlschlagen sollte!
Die stark verwandte Sprache TypeScript löst die Typprobleme eleganter
und wird im Rahmen der fortgeschrittenen "Web-Anwendungen" behandelt.
-
function calculatePrice(singlePrice, quantity, discount) {
if (typeof singlePrice !== "number") {
throw "singlePrice must be a number";
}
if (typeof quantity !== "number") {
throw "quantity must be a number";
}
if (typeof discount !== "boolean") {
throw "discount must be a boolean";
}
let toReturn = singlePrice * quantity;
if (discount) {
toReturn = toReturn * 0.85;
}
return (toReturn);
}
console.log(calculatePrice(10, 0.35, true));
console.log(calculatePrice([], {}, [true]));
Zugriff auf die Argumentliste mit arguments
- Formale Parameter einer Funktion sind lediglich ein “Vorschlag”
- Aktuelle Parameter können davon beliebig abweichen
- Problem: Zugriff auf überzählige Parameter
- Notwendig zur Implementierung von z.B.
Array.splice
- Lösung: Variable
arguments
für jeden Aufruf vordefiniert
- Enthält alle aktuellen Parameter in Form eines Objektes (das sich wie ein Array verhält)
- Vorsicht: Übermäßig “kreative” Lösungen können
arguments
überschreiben
- Ist kein Schlüsselwort der Sprache
-
const myFunc = function(p1, p2, p3) {
console.log(`p1 = ${p1}, p2 = ${p2}, p3 = ${p3}`);
console.log(arguments);
}
myFunc();
myFunc("a");
myFunc("a", "b");
myFunc("a", "b", "c");
myFunc("a", "b", "c", "d");
-
p1 = undefined, p2 = undefined, p3 = undefined
[Arguments] {}
p1 = a, p2 = undefined, p3 = undefined
[Arguments] { '0': 'a' }
p1 = a, p2 = b, p3 = undefined
[Arguments] { '0': 'a', '1': 'b' }
p1 = a, p2 = b, p3 = c
[Arguments] { '0': 'a', '1': 'b', '2': 'c' }
p1 = a, p2 = b, p3 = c
[Arguments] { '0': 'a', '1': 'b', '2': 'c', '3': 'd' }
Typische Verwendung: Optionale Parameter
- Grundidee: Nicht immer alle Parameter angeben müssen
- Standardwerte für nicht übergebene Parameter hinterlegen
-
const writeToLog = function(text, level, time) {
if (typeof (level) === "undefined") {
level = "INFO";
}
if (typeof (time) === "undefined") {
time = new Date();
}
console.log(`[${level}] ${time.toTimeString()}: ${text}`);
};
writeToLog("Test");
writeToLog("Oops", "WARN");
writeToLog("Past", "ERR ", new Date('1995-05-17T03:24:00'));
Typische Verwendung: Überladene Funktionen
- Aufgrund der dynamischen Typisierung: Beliebige übergabe von Daten an Funktionen
- Kann für polymorphe Daten auch zum Vorteil genutzt werden
- Beispiel: Entgegennehmen von Objekten oder ihren IDs
- Häufig genutzt im Kontext von Datenbankanwendungen
-
{ id: 1, username: 'Scripty McScriptFace', stars: 1023 }
Transparently retrieving user #1
{ id: 1, username: 'Scripty McScriptFace', stars: 1025 }
Bevorzugt: Iteration mit Funktionen
- Namen der Iterations-Funktionen erlauben Rückschluss über den Zweck der Iteration
for
und while
geben keinen Aufschluss über den Zweck der Schleife
- Ohne Veränderung des originalen Arrays
- Erzeugen stattdessen ein neues Array und beugen so Fehlern vor
- Lassen sich hervorragend kombinieren
- Funktionen sind komponierbare Bausteine
- Funktionsweise: Übergabe einer Funktion als “Variable”
-
- Diese so genannte “Callback-Funktion” wird dann immer wieder aufgerufen
- Die meisten Callback-Funktionen sollten bis zu drei Parameter entgegennehmen
currentValue
, das aktuelle Element, entspricht array[index]
index
, der Index des aktellen Elements
array
, das originale Array
- Abseits von Iterationsfunktionen: Callbacks für Benutzerinteraktion
- Hinterlegen einer Funktion die ausgeführt wird, wenn z.B. ein Knopf gedrückt wird
Klassische Iteration mit forEach
- Zweck: Iteration über Array, ohne dabei ein Ergebnis zu berechnen
- Typischster Fall: Ausgabe
- Ist keine Funktion, sondern eine Prozedur
- Ergebnis ist immer
undefined
-
const myPrint = function(v, i) {
console.log("Wert", v, "an Index", i);
};
["Phineas", "Ferb"].forEach(myPrint);
[1, 1, 0].forEach(myPrint);
[
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
].forEach(myPrint);
-
Wert Phineas an Index 0
Wert Ferb an Index 1
Wert 1 an Index 0
Wert 1 an Index 1
Wert 0 an Index 2
Wert [ 1, 0, 0 ] an Index 0
Wert [ 0, 1, 0 ] an Index 1
Wert [ 0, 0, 1 ] an Index 2
Beispielhafte Implementierungen
- Im Folgenden: Immer wieder mal beispielhafte Implementierung von Iterationsfunktionen
- Bitte keinesfalls in realen Projekten verwenden
- Zweck: “Magische” Funktionen entmystifizieren
- Hilft beim Verständnis von “Funktionen als Daten”
- Marginal andere Aufrufsyntax: 1. Argument ist zu iterierendes Array (nicht objektorientiert)
- Anders als die “echten Funktionen” nicht als Methode implementiert
Oberflächliche Implementierung von forEach
- Vollständige und korrekte Implementierung im MDN
- Verwendet fortgeschrittene Techniken und zeigt viele technische Details
-
const forEach = function(arr, func) {
for (let i = 0; i < arr.length; i++) {
func(arr[i], i, arr);
}
}
console.log("Aufruf 1");
forEach(["heinz","isabella"], console.log);
console.log("Aufruf 2");
forEach([true, { "agent": "p" }, 3], function(value, key) {
console.log(`arr[${key}] = `, value);
});
Finden von Werten mit find
- Zweck: Suchen eines Elementes anhand eines Prädikates
- Erstes Element, für das
true
berechnet wird, ist dann das Ergebnis von find
-
const complex_data = [
{ name: "Phineas", hair: "red" },
{ name: "Ferb", hair: "green" },
{ name: "Agent P", hair: "green" }
];
const greenHair = function(v) {
return (v.hair === "green");
};
const nameStartsWithA = function(v) {
return (v.name[0] === "A" || v.name[0] === "a");
};
console.log(complex_data.find(greenHair));
console.log(complex_data.find(nameStartsWithA));
Oberflächliche Implementierung von find
- Vollständige und korrekte Implementierung im MDN
- Verwendet fortgeschrittene Techniken und zeigt viele technische Details
-
const find = function(arr, func) {
for (let i = 0; i < arr.length; i++) {
if (func(arr[i], i, arr)) {
return (arr[i]);
}
}
return (undefined);
}
console.log(find([1,2], function(v) { return (v == 1); }));
console.log(find([1,2], function(v) { return (v >= 1); }));
console.log(find([1,2], function(v) { return (v >= 2); }));
console.log(find([1,2], function(v) { return (v > 2); }));
Praktischer Exkurs: Arrow-Notation für Funktionen
- Permanente Erwähnung von
function
, return
und geschweiften Klammern ist mühselig
- Und für später: Hat Einfluss auf den
this
-Kontext
- (param1, param2, …, paramN) => { statements }
- (param1, param2, …, paramN) => expression
-
=>
statt function
, Argumente werden davor notiert
- Bei nur einem Argument: Klammern optional
- Bei keinem Argument:
()
oder _
angeben
- Bei exakt einem Ausdruck im Rumpf: Geschweifte Klammern und
return
optional
-
const complex_data = [
{ name: "Phineas", hair: "red" },
{ name: "Ferb", hair: "green" },
{ name: "Agent P", hair: "green" }
];
complex_data.forEach(v => console.log(v.name));
console.log(complex_data.find(v => v.name === "Phineas"));
Filtern von Werten mit filter
- Zweck: Entfernen von unerwünschten Werten aus dem Array
- Resultat ist ein neues Array
-
const complex_data = [
{ name: "Phineas", hair: "red" },
{ name: "Ferb", hair: "green" },
{ name: "Agent P", hair: "green" },
{ name: "Candice", hair: "blonde" },
]
console.log(
"Greenheads:\n",
complex_data.filter(v => v.hair === "green")
);
console.log(
"Non Greenheads:\n",
complex_data.filter(v => v.hair !== "green")
);
console.log(
"Short Names:\n",
complex_data.filter(v => v.name.length < 7)
);
console.log(
"Long Names:\n",
complex_data.filter(v => v.name.length > 7)
);
-
Greenheads:
[ { name: 'Ferb', hair: 'green' },
{ name: 'Agent P', hair: 'green' } ]
Non Greenheads:
[ { name: 'Phineas', hair: 'red' },
{ name: 'Candice', hair: 'blonde' } ]
Short Names:
[ { name: 'Ferb', hair: 'green' } ]
Long Names:
[]
Oberflächliche Implementierung von filter
- Vollständige und korrekte Implementierung im MDN
- Verwendet fortgeschrittene Techniken und zeigt viele technische Details
-
const filter = function(arr, func) {
const toReturn = [];
for (let i = 0; i < arr.length; i++) {
if (func(arr[i], i, arr)) {
toReturn.push(arr[i]);
}
}
return (toReturn);
}
console.log(filter([1,2,3,4], v => v >= 1));
console.log(filter([1,2,3,4], v => v > 1));
console.log(filter([1,2,3,4], v => v == 2));
-
[ 1, 2, 3, 4 ]
[ 2, 3, 4 ]
[ 2 ]
Prüfen von Eigenschaften mit some
und every
- Zweck: Prüfen ob mindestens eins (
some
) oder alle (every
) Elemente eines Arrays ein Prädikat erfüllen
- Entspricht dem logischen
OR
bzw. dem logischen AND
-
const complex_data = [
{ name: "Phineas", hair: "red" },
{ name: "Ferb", hair: "green" },
{ name: "Agent P", hair: "green" },
{ name: "Candice", hair: "blonde" },
]
console.log("Alle grüne Haare?",
complex_data.every(p => p.hair === "green"));
console.log("Irgendwer grüne Haare?",
complex_data.some(p => p.hair === "green"));
Oberflächliche Implementierung von some
und every
(I)
- Vollständige und korrekte Implementierung im MDN
- Verwendet fortgeschrittene Techniken und zeigt viele technische Details
-
const some = function(arr, func) {
return (arr.filter(func).length >= 1);
}
const every = function(arr, func) {
return (arr.filter(func).length == arr.length);
}
const onlyThree = v => v == 3;
console.log([1,2,3]);
console.log("Enthält 3?", some([1,2,3], onlyThree));
console.log("Nur 3?", every([1,2,3], onlyThree));
console.log([3]);
console.log("Enthält 3?", some([3], onlyThree));
console.log("Nur 3?", every([3], onlyThree));
console.log([1]);
console.log("Enthält 3?", some([1], onlyThree));
console.log("Nur 3?", every([1], onlyThree));
Oberflächliche Implementierung von some
und every
(II)
- Vollständige und korrekte Implementierung im MDN
- Verwendet fortgeschrittene Techniken und zeigt viele technische Details
-
const some = function(arr, func) {
for (let i = 0; i < arr.length; i++) {
if (func(arr[i], i, arr)) {
return (true);
}
}
return (false);
}
const every = function(arr, func) {
for (let i = 0; i < arr.length; i++) {
if (!func(arr[i], i, arr)) {
return (false);
}
}
return (true);
}
const onlyThree = v => v == 3;
console.log([1,2,3]);
console.log("Enthält 3?", some([1,2,3], onlyThree));
console.log("Nur 3?", every([1,2,3], onlyThree));
console.log([3]);
console.log("Enthält 3?", some([3], onlyThree));
console.log("Nur 3?", every([3], onlyThree));
console.log([1]);
console.log("Enthält 3?", some([1], onlyThree));
console.log("Nur 3?", every([1], onlyThree));
- Zweck: Veränderung jedes einzelnen Wertes im Array
- Resultat ist ein neues Array
-
console.log([1,2,3].map(v => v + 1));
console.log([
"Remote Control",
"Audience Control",
"Cute-Puppy-Call"
].map(v => v + "-inator"));
-
[ 2, 3, 4 ]
[ 'Remote Control-inator',
'Audience Control-inator',
'Cute-Puppy-Call-inator' ]
Oberflächliche Implementierung von map
- Vollständige und korrekte Implementierung im MDN
- Verwendet fortgeschrittene Techniken und zeigt viele technische Details
-
const map = function(arr, func) {
const toReturn = [];
for (let i = 0; i < arr.length; i++) {
toReturn.push(func(arr[i]));
}
return (toReturn);
}
console.log(map([1,2,3], v => v + 1));
console.log(map([1,2,3], v => v * 3));
console.log(map(
["Kind", "Eimer", "Bagger", "Massaker", "Regenschirm"],
v => v + "chen"
));
-
[ 2, 3, 4 ]
[ 3, 6, 9 ]
[ 'Kindchen',
'Eimerchen',
'Baggerchen',
'Massakerchen',
'Regenschirmchen' ]
-
const extractName = o => o.name;
const length = s => s.length;
const complex_data = [
{ name: "Phineas", hair: "red" },
{ name: "Ferb", hair: "green" },
]
console.log(
complex_data
.map(extractName)
);
console.log(
complex_data
.map(extractName)
.map(length)
);
console.log(
complex_data
.map(extractName)
.map(length)
.map(v => v % 2 === 0)
);
map
und Umgang mit Objekten
- Problematisch:
map
ersetzt möglicherweise ein komplexes Objekt durch einen einzigen Wert
- Erschwert Kombination unterschiedlicher Primitiven
-
const complex_data = [
{ name: "Matt", hair: "brown", born: 1978 },
{ name: "Chris", hair: "brown", born: 1978 },
{ name: "Dominic", hair: "blonde", born: 1977 },
]
const currentAge = function(person) {
let currentYear = new Date().getFullYear();
person.age = (currentYear - person.born);
return (person);
}
console.log(
"Mapped Data:\n",
complex_data.map(currentAge));
console.log(
"Original Data:\n",
complex_data
);
-
Mapped Data:
[ { name: 'Matt', hair: 'brown', born: 1978, age: 41 },
{ name: 'Chris', hair: 'brown', born: 1978, age: 41 },
{ name: 'Dominic', hair: 'blonde', born: 1977, age: 42 } ]
Original Data:
[ { name: 'Matt', hair: 'brown', born: 1978, age: 41 },
{ name: 'Chris', hair: 'brown', born: 1978, age: 41 },
{ name: 'Dominic', hair: 'blonde', born: 1977, age: 42 } ]
map
und tiefe Kopien von Objekten
- Lösungsansatz: Vor jeder Veränderung eine Kopie der Eingabe erstellen
- Funktioniert nicht bei Objekten mit zirkulären Referenzen
-
const complex_data = [
{ name: "Matt", hair: "brown", born: 1978 },
{ name: "Chris", hair: "brown", born: 1978 },
{ name: "Dominic", hair: "blonde", born: 1977 },
]
const deepCopy = function(obj) {
// Nicht-so-eleganter-Hack
return (JSON.parse(JSON.stringify(obj)));
}
const currentAge = function(person) {
let currentYear = new Date().getFullYear();
person.age = (currentYear - person.born);
return (person);
}
console.log(
"Mapped Data:\n",
complex_data
.map(deepCopy)
.map(currentAge));
console.log(
"Original Data:\n",
complex_data
);
-
Mapped Data:
[ { name: 'Matt', hair: 'brown', born: 1978, age: 41 },
{ name: 'Chris', hair: 'brown', born: 1978, age: 41 },
{ name: 'Dominic', hair: 'blonde', born: 1977, age: 42 } ]
Original Data:
[ { name: 'Matt', hair: 'brown', born: 1978 },
{ name: 'Chris', hair: 'brown', born: 1978 },
{ name: 'Dominic', hair: 'blonde', born: 1977 } ]
Arrays aggregieren mit reduce
- Zweck: Verdichtet ein Array von Werten auf einen einzigen Wert
- Typische Spezialisierungen:
sum
, count
, min
, max
- Veränderte Callback-Signatur: Zusätzlicher erster Parameter
-
akkumulator
, das Ergebnis der vorherigen Berechnung
currentValue
, das aktuelle Element, entspricht array[index]
index
, der Index des aktellen Elements
array
, das originale Array
- Veränderte Aufruf-Signatur:
reduce(callback, [initialValue])
- Fungiert als initialer Akkumulator, Wird kein
initialValue
übergeben, wird der erste Wert des Arrays verwendet
- Der berechnete Wert jedes Aufrufs der Callback-Funktion fungiert als Akkumulator für den nächsten Aufruf
- Das Ergebnis des letzten Aufrufs der Callback-Funktion ist dann das Ergebnis des Aufrufs von
reduce
Ausführliches Beispiel zu reduce
-
[4,1,3,5,2].reduce(
(accumulator, currentValue, currentIndex, array) => accumulator + currentValue;
);
- Dieser Aufruf von
reduce
ruft seinerseits die übergebene Callback-Funktion vier Mal auf
- Den vier Aufrufen werden dabei die folgenden Werte übergeben:
accumulator |
currentValue |
currentIndex |
array |
return |
4 |
1 |
1 |
[4,1,3,5,2] |
5 |
5 |
3 |
2 |
[4,1,3,5,2] |
8 |
8 |
5 |
3 |
[4,1,3,5,2] |
13 |
13 |
2 |
4 |
[4,1,3,5,2] |
15 |
- Vorsicht:
currentIndex
beginnt in diesem Beispiel bei 1
- Der Wert an Index
0
ist der initiale Wert für den Akkumulator, da kein separater Wert übergeben worden ist.
Kurze Beispiele zu reduce
-
const size = (akku, _) => akku + 1;
const add = (lhs, rhs) => lhs + rhs;
const mult = (lhs, rhs) => lhs * rhs;
const numbers = [24,12,46,1,0,4,7,2];
console.log(numbers.reduce(size));
console.log(numbers.reduce(size, 0));
console.log(numbers.reduce(add));
console.log(numbers.reduce(mult))
Oberflächliche Implementierung von reduce
(I)
- Vollständige und korrekte Implementierung im MDN
- Verwendet fortgeschrittene Techniken und zeigt viele technische Details
-
const reduce = function(arr, func, init) {
let accu = init;
for(let i =0; i < arr.length; i++) {
accu = func(accu, arr[i]);
}
return (accu);
}
console.log("+ mit 0:", reduce([1,2,3], (a, v) => a + v, 0));
console.log("+ mit 1:", reduce([1,2,3], (a, v) => a + v, 1));
console.log("* mit 0:", reduce([1,2,3], (a, v) => a * v, 0));
console.log("* mit 1:", reduce([1,2,3], (a, v) => a * v, 1));
Oberflächliche Implementierung von reduce
(II)
- Vollständige und korrekte Implementierung im MDN
- Verwendet fortgeschrittene Techniken und zeigt viele technische Details
-
const reduce = function(arr, func, init) {
let i = 0;
let accu = init;
if (typeof init === "undefined")
{
accu = arr[0];
i = 1;
}
for(;i < arr.length; i++) {
accu = func(accu, arr[i]);
}
return (accu);
}
console.log("+ mit 0:", reduce([1,2,3], (a, v) => a + v, 0));
console.log("+ ohne :", reduce([1,2,3], (a, v) => a + v));
console.log("* mit 0:", reduce([1,2,3], (a, v) => a * v, 0));
console.log("* ohne :", reduce([1,2,3], (a, v) => a * v));
Kombination von filter
und reduce
-
const add = (lhs, rhs) => lhs + rhs;
const mult = (lhs, rhs) => lhs * rhs;
const isEven = (val) => val % 2 === 0;
const numbers = [24,12,46,1,0,4,7,2];
console.log(
numbers
.filter(isEven)
.reduce(add)
);
console.log(
numbers
.filter(isEven)
.reduce(mult)
);
console.log(
numbers
.filter(isEven)
.filter(v => v > 0)
.reduce(mult)
);
console.log(
numbers
.filter(v => v % 2 === 0 && v > 0)
.reduce(mult)
);
Kombination von map
und reduce
(I)
map
zur Extraktion des Wertes costs
aus den komplexen Daten
- Bewusster Informationsverlust
-
const add = (lhs, rhs) => lhs + rhs;
const complex_data = [
{ name: "Remote Control", costs: 4 },
{ name: "Audience Control", costs: 7 },
{ name: "Cute-Puppy-Call", costs: 8 },
]
console.log(
complex_data
.map(v => v.costs)
.reduce(add)
);
Kombination von map
und reduce
(II)
map
berechnet einen abgeleiteten Wert
Menge * Preis
-
const add = (lhs, rhs) => lhs + rhs;
const complex_data = [
{ name: "Remote Control", costs: 4, amount: 2 },
{ name: "Audience Control", costs: 7, amount: 3 },
{ name: "Cute-Puppy-Call", costs: 8, amount: 1 },
]
console.log(
complex_data
.map(v => v.costs * v.amount)
.reduce(add)
);
Kombination von map
und reduce
(III)
map
berechnet einen abgeleiteten Wert
- Summe aller Preise in einer Liste (wiederrum mit
reduce
)
-
const add = (lhs, rhs) => lhs + rhs;
const complex_data = [
{ name: "Remote Control", costs: [4, 12, 25] },
{ name: "Audience Control", costs: [65, 62] },
{ name: "Cute-Puppy-Call", costs: [32, 78] },
]
console.log(
complex_data
.map(v => v.costs.reduce(add))
.reduce(add)
);
Mit ein bisschen Übung: Iterationsfunktionen schreiben sich wie SQL
- Einige Operationen mit unmittelbaren Entsprechungen
-
filter
entspricht where
map
entspricht select
reduce
entspricht sum
, min
, max
, …
Der wesentliche Vorteil dieser Funktionen ist die bessere Lesbarkeit! Im Gegensatz zu Schleifen ist der Zweck jeder Iteration klar definiert.
- Schlagwort
LINQ
: “Language Integrated Queries”
SQL
-artige Anweisungen in Sprachen wie C# funktionieren intern exakt wie hier gezeigt
- Fehlende Operationen kann man sich auch selber schreiben
- Wie gezeigt: Eigene Iterationsfunktionen sind kein Hexenwerk
Neue Primitive: groupBy
- Gewünschtes Ergebnis: Kategorisierung von Werten
-
- Callback-Funktion extrahiert den relevanten Wert aus dem Objekt
- Ergebnis ist ein Objekt mit einem Array je Kategorie
-
const complex_data = [
{ name: "Remote Control", costs: 4, inventor: "heinz" },
{ name: "Swinter", costs: 4, inventor: "ferb" },
{ name: "Audience Control", costs: 7, inventor: "heinz" },
{ name: "Cute-Puppy-Call", costs: 8, inventor: "heinz" },
]
const groupBy = function(arr, keyFunc) {
const toReturn = {};
arr.forEach(v => {
const key = keyFunc(v);
const items = toReturn[key];
if (items) {
items.push(v);
} else {
toReturn[key] = [v];
}
});
return (toReturn);
}
console.log(groupBy(complex_data, v => v.inventor));
console.log(groupBy(complex_data, v => v.costs));
-
{ heinz:
[ { name: 'Remote Control', costs: 4, inventor: 'heinz' },
{ name: 'Audience Control', costs: 7, inventor: 'heinz' },
{ name: 'Cute-Puppy-Call', costs: 8, inventor: 'heinz' } ],
ferb: [ { name: 'Swinter', costs: 4, inventor: 'ferb' } ] }
{ '4':
[ { name: 'Remote Control', costs: 4, inventor: 'heinz' },
{ name: 'Swinter', costs: 4, inventor: 'ferb' } ],
'7':
[ { name: 'Audience Control', costs: 7, inventor: 'heinz' } ],
'8': [ { name: 'Cute-Puppy-Call', costs: 8, inventor: 'heinz' } ] }
Fehlerbehandlung mit try
und catch
- Typische Syntax mit
try
- und catch
-Blöcken
-
- Im Rahmen dieser Vorlesung: Nur zum behandeln von Fehlern in der Standardbibliothek nötig
- Ausnahmen müssen nicht selbstständig geworfen werden
Besonderheiten von JavaScript
- (Überspitzter) ursprünglicher Zweck von JavaScript: Eine Sprache für unnötige Animationen
- Das hat sich geändert
- Ursprüngliche Zielgruppe: Nicht-Programmierer
- Ursprüngliches Ergebnis: Nicht-Programm (?)
- Lesenswertes Interview mit dem “Erfinder” von JavaScript: Brendan Eich
- Macht deutlich: Viele der Probleme resultieren aus der Annahme, dass ein Programm schon nicht länger als 100 Zeilen werden würde
- Pragmatische Faustregel: Wenn JavaScript die Möglichkeit hat etwas unsinniges zu tun oder krachend abzubrechen …
- … wird es etwas unsinniges tun.
Polyfills
- Aufgrund der dynamischen Natur von JavaScript: Erweiterung der Standardbibliothek zur Laufzeit
- Bereitstellung von neuen Features in alten Laufzeitumgebungen
- Alle hier gezeigten Iterationsfunktionen lassen sich auch in älteren Browsern nutzen
- Code wird dann nicht vom Browser, sondern vom Programm bereitgestellt
Im Zusammenhang mit Objektorientierter Programmierung: Thema der fortgeschrittenen Veranstaltung!
Automatic Semicolon Insertion
- Automatisches einfügen von Semikoli, die man vergessen haben könnte
- Beschrieben im ECMA Standard 262, Kapitel 11.9.1
- Regel lautet (stark verkürzt): “Wenn eine unerwartete neue Zeile oder
}
gefunden wird, füge ein Semikolon ein. Wenn der Quelltext dann gültig ist, ist er bestimmt genau so gemeint.”
- Tatsächlich sind es drei Regeln, mit relativ vielen komplizierten Details
null
und undefined
- Viele Sprachen haben einen “undefinierten” Wert, JavaScript hat zwei
-
- Intern genutzt wird jedoch nur
undefined
- Der Wert
null
muss explizit vom Benutzer vergeben werden
null == undefined
, aber null !== undefined
Einfachster Ausweg: Ignorieren Sie die Existenz des Wertes null
und verwenden Sie grundsätzlich undefined
als "fehlenden" Wert.
Strikter Modus
- Schaltet gefährliche oder mehrdeutige Funktionen von JavaScript ab
-
- Inkompatibel zu älteren Quelltexten
- Lesetipp (nach dem Kapitel über fortgeschrittenes JavaScript): Dokumentation im MDN
- Kann für Dateien oder für Funktionen aktiviert werden
"use strict";
als erste Zeile der Datei oder des Funktionsrumpfes
- Schaltet leider nicht das automatische Einfügen von Semikoli ab
- Einzige Abhilfe: Korrekten Code schreiben
Tipp für die Übung: Verwenden Sie "use strict";
als erste Anweisung in jeder Datei. Dadurch werden sehr wenige, aber dafür sehr gravierende, "leise" Fehler zu lauten Abstürzen.
-
undefined = Math.PI;
console.log(undefined);
false.hello = "hello";
console.log(false.hello);
let myVar = 42;
myVar.value = "zweiundvierzig"
console.log(myVar.value);
-
'use strict';
undefined = Math.PI;
console.log(undefined);
false.hello = "hello";
console.log(false.hello);
let myVar = 42;
myVar.value = "zweiundvierzig"
console.log(myVar.value);
“Casting”-Abkürzungen
- JavaScript sieht keine Syntax vor, um einen bestimmten Datentypen zu erzwingen
- Fällt bei
if
Ausdrücken nicht auf, wohl aber bei JSON
-Dokumenten
- Abhilfe mit kruder Notation
-
- Doppelte Negation mit
!!
erzwingt einen booleschen Wert
- Unäres
+
vor einer Variable erzwingt einen Zahlenwert
-
const complex_data = [
{ name: "Matt", born: "1978" },
{ name: "Chris", born: 1978 },
{ name: "Dominic", born: "1977" },
]
function hasPerson(name) {
return (complex_data.find(person => person.name === name));
}
console.log("Phineas? ", hasPerson("Phineas"));
console.log("Matt? ", hasPerson("Matt"));
console.log("Phineas!!?", !!hasPerson("Phineas"));
console.log("Matt!!? ", !!hasPerson("Matt"));
function getAge(person) {
return (+(person.born));
}
console.log("Roher Wert:", JSON.stringify(complex_data[0].born));
console.log("Roher Wert:", JSON.stringify(complex_data[1].born));
console.log("Zahlwert: ", JSON.stringify(getAge(complex_data[0])));
console.log("Zahlwert: ", JSON.stringify(getAge(complex_data[1])));
(Noch) kein einheitliches Modulsystem
- Wie sollten komplizierte Programme ausgeliefert werden?
-
- Historische Antwort: Es gibt keine komplizierten Programme in JavaScript
- Aktuelle Antwort: Es gibt (mindestens) drei mögliche Modulsysteme dafür: AMD, CommonJS, ECMA Script 6
- Ursprünglicher Ansatz: Skripte einfach in der korrekten Reihenfolge laden
-
- Sehr fehleranfällig, Abhängigkeiten müssen manuell gepflegt werden
- Komplizierter Umgang mit zyklischen Abhängigkeiten
- Sehr einfache Umsetzung ohne Kompilierungsschritt
- “Verbesserung”: Alle Skripte in einer Datei zusammenfassen
-
- Erzeugt ohne weitere Maßnahmen häufig eine unnötig große Datei
- Erzwingt einen Kompilierungsschritt
- Aktuell: Modulsystem mit ECMAScript 6 standarisiert
- Setzt sich hoffentlich möglichst schnell durch
- Problematisch: Unterschiedliche Ladevorgänge für JavaScript im Browser oder auf dem Server
-
- Server hat Zugriff auf das Dateisystem, kann dynamisch Dateien nachladen
- Mehr und mehr historisch: Browser laden keinen Quelltext nach