05: Einführung in die Programmierung
Um einem Computer sagen zu können, was er zu tun hat, muss man wissen, wie ein Computer „denkt“. Man muss nicht gut in Mathe sein, um programmieren zu können, man muss nur logisch denken und klare Anweisungen geben können.
Inhalt
Was kann ein Computer?
Computer sind eigentlich ziemlich dumm, sie können vielleicht gut rechnen, wie der Name es schon sagt, aber außer dem können sie ohne Hilfe wirklich nicht viel. Unsere Aufgabe als Entwickler ist es, dem Computer zu erklären, wie er die Aufgaben lösen kann, die wir ihm aufgeben. Wir übersetzen also abstrakte Konzepte in klare Anweisungen, die der Rechner dann ausführen kann.
Wo liegen also die Stärken eines Computers:
- Rechnen
- Monotone Wiederholungen
- Anweisungen zu 100% genau befolgen
- Mit großen Datenmengen umgehen und den Überblick bewahren
Und seine Schwächen:
- Kreativ denken
- Denken überhaupt
- Mit vagen Anweisungen klarkommen
- Interpretieren
- Usw.
Im Prinzip kann ein Computer also nur genau das Ausführen, was ihr ihm sagt. Wie genau das funktioniert liegt ein bisschen außerhalb dieses Kurses, aber für diejenigen unter euch, die es interessiert: ihr könnt einfach einmal in die Rechnerarchitektur- und Betriebssysteme-Vorlesungen an der LMU hineinschnuppern. Die bringen das ganze eigentlich auf den Punkt.
Für uns wichtig ist aber: wie sage ich dem Computer, was er machen soll? Und vor allem, wie er es machen soll, denn das weiß er auch nicht.
Was ist eine Programmiersprache?
Viele von euch werden bereits gehört haben, dass ein Computer „mit Einsen und Nullen“ funktioniert. Das steht sinnbildlich für die beiden Zustände, die ein Computer beherrscht: Strom an und Strom aus. Da es sich hierbei nur um zwei Zustände handelt, ist es ein binäres System. Man kann alle möglichen Daten binär ausdrücken, vor allem natürlich Zahlen, die für einen Rechner wichtig sind, aber auch Buchstaben und viel mehr. Während eines Informatikstudiums würdet ihr auch lernen, wie man Binärcode liest und schreibt, aber selbst ein Informatiker würde das nicht freiwillig machen, wenn es nicht absolut nötig wäre.
Deshalb wurden schon in den Anfangszeiten der Computer sogenannte Programmiersprachen entwickelt, die es Menschen erlauben, für sie verständlicheren Code zu schreiben, der dann für den Computer in Binärcode übersetzt wird. Diese „Übersetzung“ nennt man „Kompilierung“.
Kompiliert vs. Interpretiert
Je nachdem wann diese Kompilierung stattfindet, spricht man von einer kompilierten oder interpretierten Programmiersprache. Bei einer kompilierten Programmiersprache wie C oder C++ muss euer Code zunächst von einem Compiler komplett übersetzt werden, bevor ihr euer Programm ausführen könnt. Bei einer interpretierten Programmiersprache wie Python oder JavaScript hingegen wird euer Code Zeile für Zeile von einem sogenannten Interpreter übersetzt während das Programm schon läuft.
Deshalb könnt ihr zum Beispiel einfach node
in eurem Terminal ausführen und so
ein Programm schreiben, das sofort ausgeführt wird:
> let x = 2; // Variable x wird der Wert 2 zugewiesen
> let y = 5; // Variable y wird der Wert 4 zugewiesen
> x + y; // Variable x wird mit Variable y summiert
7 // das Ergebnis wird sofort angezeigt
> let z = x + y; // x und y sind weiterhin definiert, Variable z wird der Wert 7 zugewiesen
Tipp:
Code-Blöcke, die mit >
beginnen, stehen fortan für Code, den
ihr in Node ausführen könnt, d.h. nachdem ihr in einem Terminal node
eingegeben habt. Beginnt eine Zeile in einem solchen Block nicht mit >
,
bedeutet das, dass es sich um die Ausgabe von Node handelt.
Das hat den enormen Vorteil, dass ihr schnell etwas ausprobieren könnt, z.B. was
passiert wenn ihr 56 % 2
in JavaScript ausführt, ohne gleich ein ganzes
Programm schreiben und kompilieren zu müssen. Eine kompilierte Sprache hat
allerdings auf der anderen Seite natürlich Performance-Vorteile, denn ein
Compiler kann während der Übersetzung den Code noch weiter optimieren.
Viele verschiedene Sprachen
Aber Amadeus, warum gibt es denn so viele verschiedene Programmiersprachen?
Programmiersprachen werden für die unterschiedlichsten Zwecke und Plattformen entwickelt. So hat zum Beispiel Apple mit Swift eine eigene Programmiersprache für die Entwicklung auf iOS-Systemen geschaffen, während Google Dart und Kotlin für Android unterstützt. Andere Programmiersprachen wie C++ versuchen bestehende Programmiersprachen, in diesem Fall C, zu verbessern und für den Programmierer angenehmer zu machen.
Je nach Programmiersprache gibt es zum Beispiel auch unterschiedliche
Schlüsselwörter (z.B. let
, if
) und Befehle, sowie andere Regeln, zum
Beispiel, wann man ein Semikolon benutzen muss und wann nicht. Diese
Schlüsselwörter und Regeln nennt man die Syntax einer Programmiersprache, also
ihre „Grammatik“.
Da am Ende allerdings immer für den Computer verständlicher Binärcode (oder Vergleichbares) dabei herauskommt, kann man theoretisch mit jeder Programmiersprache ein Programm für jede Plattform schreiben. (Praktisch ist das allerdings enorm umständlich und daher nicht zu empfehlen, weil man z.B. einen eigenen Compiler oder Interpreter schreiben müsste, die wiederum schon im Maschinencode vorliegen müssten, um das Programm zu übersetzen.)
Programmierparadigmen
Unterschiedliche Programmiersprachen bringen auch unterschiedliche Paradigmen mit sich, also verschiedene Programmier-Stile, wobei sich meistens mehrere Stile mit der selben Programmiersprache umsetzen lassen.
Zwei der größten Paradigmen, die sich in viele weitere Sub-Paradigmen unterteilen lassen, sind die folgenden:
Imperative Programmierung ist ein ein Stil, bei dem dem Computer direkte Befehle gegeben werden, die dieser dann nach und nach abarbeitet. Diese Anweisungen führen zu Ergebnissen, die dann mit weiteren Anweisungen weiterverarbeitet werden können. Wir werden hauptsächlich mit diesem Stil arbeiten.
Deklarative Programmierung ist das genaue Gegenteil von imperativer Programmierung. Hier beschreibt der Programmierer die Ergebnisse, die er / sie erhalten möchte und die Programmiersprache liefert diese Ergebnisse (meistens während der Interpretation) an den Programmierer zurück.
Typisierung
Ein weiteres Unterscheidungsmerkmal zwischen Programmiersprachen ist die sogenannte „Typisierung“. Vereinfacht gesagt legt diese fest, ob Variablen, die ihr deklariert einen festen Typ, z.B. eine Zeichenkette, oder eine Zahl haben, oder ob dieser Typ sich im Verlauf des Programms ändern kann.
JavaScript hat keine strikte Typisierung. Deshalb müsst ihr den Typ einer Variablen nicht festlegen, wenn ihr sie deklariert und ihr könnt der selben Variable einen Wert von einem anderen Typen zuweisen, so lang die Variable keine Konstante ist. Ich würde euch allerdings davon abraten, das zu tun, denn es kann besonders bei komplexeren Programmen schnell zu ungewollten Fehlern führen.
> let x; // Variable x wird definiert, aber hat keinen Wert
> typeof x; // Mit typeof könnt ihr den Typ einer Variable ausgeben lassen
'undefined' // x hat den Typ 'undefined'
> x = 1; // x wird der Wert 1 zugewiesen
> typeof x;
'number' // jetzt hat x den Typ 'number', ist also eine Zahl
> x = '1'; // x wird der Wert '1' zugewiesen
> typeof x;
'string' // x hat jetzt den Typ 'string', weil '1' keine Zahl, sondern eine Zeichenkette ist
> const y = 15; // y ist eine Konstante mit dem Wert 15
> typeof y;
'number'
> y = 'hallo'; // wirft einen Fehler, weil eine Konstante nicht verändert werden kann
Uncaught TypeError: Assignment to constant variable.
Was ist das einfachste Programm?
Das einfachste Programm der Welt ist wahrscheinlich die simple Addition zweier
Zahlen: 1 + 1
. Der Rechner führt diesen Befehl aus und gibt das Ergebnis
zurück: 2
. Trotzdem wird als Beispiel für das einfachste Programm aus
Tradition das sogenannte Hello-World-Programm benutzt.
In JavaScript ist ein einfaches Hello-World nur eine Zeile lang:
> console.log('Hello World!'); // gibt 'Hello World' auf der Konsole aus
Programmierer sind allerdings faul, also warum mehr tippen als nötig? Schreiben wir unser Programm also lieber als eine einfache Funktion:
// hello.js
function sayHello() { // neue Funktion mit dem Namen 'sayHello'
console.log('Hello World!');
}
// die Funktion muss aufgerufen werden, damit etwas passiert
sayHello();
sayHello();
sayHello();
sayHello();
Nun können wir unser Programm mit node hello.js
ausführen und erhalten gleich
vier „Hello World!“. 😉
Wir können es aber auch leichter erweitern:
// hello.js
function sayHello(name = 'World') {
// name ist der Wert, der der Funktion beim Aufruf übergeben wird
// wird nichts übergeben, ist der Wert von name 'World'
console.log(`Hello ${name}!`); // Backticks (`) erlauben String-Templates, also Variablen in einer Zeichenkette
}
sayHello(); // Ausgabe: Hello World!
sayHello('Gustav'); // Ausgabe: Hello Gustav!
sayHello('Norbert'); // Ausgabe: Hello Norbert!
Und vielleicht ein letztes Mal:
// hello.js
function sayHello(name = 'World') {
// if leitet eine Bedingung ein
if (name === 'Amadeus') { // wenn name genau gleich 'Amadeus'
console.log('Hello Teacher!'); // dann führe diese Zeile aus
} else { // wenn nicht
console.log(`Hello ${name}!`); // führe diese Zeile aus
}
}
sayHello(); // Ausgabe: Hello World!
sayHello('Gustav'); // Ausgabe: Hello Gustav!
sayHello('Norbert'); // Ausgabe: Hello Norbert!
sayHello('Amadeus'); // Ausgabe: Hello Teacher!
Habt ihr hello.js
geschrieben, ausgeführt und verstanden, dann herzlichen
Glückwunsch: ihr seid jetzt ein Programmierer. 😉 Mehr ist wirklich nicht
dabei.
Was sind die Grundkonzepte?
Es gibt bestimmte Grundkonzepte, die in nahezu jeder (imperativen) Sprache vorhanden sind. Auf die werde ich nun am konkreten Beispiel JavaScript näher eingehen. Einige dieser Konzepte haben wir bereits in den vorhergehenden Beispielen gesehen, andere sind etwas komplexer, aber lasst uns nicht vergessen: der Computer ist dumm, er braucht klare und logische Anweisungen, deshalb haben die nachfolgenden Konzepte auch immer einen logischen Sinn.
Befehle / Anweisungen
Wie wir in unserem Hello-World Beispiel gesehen haben, geben wir bei der imperativen Programmierung unserem Computer konkrete Befehle. Diese Befehle nennt man auch „Statements“ und während JavaScript es durchaus erlaubt, mehrere Statements in eine Zeile zu schreiben und nur durch Semikolons (;) zu trennen, ist es für sauberen Code sinnvoll in den meisten Fällen nur eine Anweisung pro Zeile zu schreiben.
Beispiel:
let x = 1;
let y = 2;
let z = x + y;
console.log(x, y, z); // Ausgabe: 1 2 3
// Auch möglich, aber weniger lesbar:
let u = 'foo'; let v = 'bar'; console.log(u + v); // Ausgabe: foobar
Anweisungen sind zum Beispiel Variabelzuweisungen und Funktionsaufrufe.
Kommentare
Damit ihr euren Code später noch einmal versteht und vor allem auch Andere, die euren Code lesen verstehen, was ihr meint, solltet ihr Kommentare hinterlassen.
In JavaScript gibt es zwei Formen von Kommentaren: einzeilige Kommentare, die mit
//
eingeleitet werden, und mehrzeilige Kommentare, die mit /* */
umgeben
werden:
// dies ist ein einzeiliger Kommentar
let x = 1; // er kann auch nach einem Statement stehen
/* Dies ist ein mehrzeiliger Kommentar
Er unterstützt Zeilenumbrüche. */
Alles, was innerhalb eines Kommentars steht, wird vom Computer komplett ignoriert. Hier noch ein paar Richtlinien, wann Kommentare Sinn machen:
- Wenn der Code nicht selbsterklärend ist
- Wenn ihr einen Hack oder einen Workaround verwenden müsst, dann aber auch mit Begründung, weshalb
- Wenn ihr eine komplexe Funktion definiert, um Anderen klarer zu machen, was genau passiert
Wenn euer Code sauber ist, d.h. korrekte Einrückung verwendet, sinnvolle Variablennamen benutzt, und sich nicht unnötig wiederholt, werdet ihr nicht viele Kommentare brauchen, denn er sollte dann selbsterklärend sein. Seht es so: wenn ihr Kommentare benötigt, um den Code zu verstehen, könnt ihr ihn wahrscheinlich so verbessern, dass ihr die Kommentare nicht mehr braucht.
Da viele von euch allerdings noch Anfänger sind, die noch nicht so viel Code gelesen haben, werde ich natürlich weiterhin viele Kommentare in den Codebeispielen hinterlassen und ihr könnt euch natürlich auch Notizen an euren Code schreiben, damit ihr später noch nachvollziehen könnt, was er macht.
Variablen
In mehreren Beispielen ist nun schon das Wort „Variable“ gefallen. Viele von euch werden sich wohl noch an den Mathematikunterricht in der Oberstufe erinnern, in dem ihr auch schon mit Variablen in Berührung gekommen seid. Grob beschrieben ist eine Variable einfach eine Box, in die ihr einen Wert stecken könnt.
In JavaScript kann dieser Wert alles Mögliche sein, eine Zahl, eine Zeichenkette, sogar eine ganze Funktion. Ihr werdet in eurem Code Variablen benutzen, um Informationen für die Dauer der Ausführung des Programms zu speichern.
Wie man Variablen deklariert, haben wir in vielen Beispielen schon gesehen:
> let x; // die Variable x wird deklariert
Ihr könnt einer Variable natürlich auch schon während der Deklaration einen Wert zuweisen:
> let x = 1; // die Variable x wird deklariert und erhält den Wert 1
Habt ihr eine Variable deklariert und ihr einen Wert zugewiesen, könnt ihr diesen Wert natürlich zu einem späteren Zeitpunkt wieder ändern, indem ihr ihr einfach einen neuen Wert zuweist, passt dabei aber auf, dass ihr nicht versucht die Variable neu zu deklarieren, denn das führt zu einem Fehler:
> let x = 1; // die Variable x wird deklariert und erhält den Wert 1
> console.log(x);
1
> x = 2; // der Variable x wird der Wert 2 zugewiesen
> console.log(x);
2
> let x = 3 // funktioniert nicht
Uncaught SyntaxError: Identifier 'x' has already been declared
Achtung:
JavaScript erlaubt es euch auch, Variablen zu deklarieren, indem ihr einfach
variableName = wert;
schreibt (also ohne let
). Das
ist ein Überbleibsel aus der Vergangenheit und sollte nicht
verwendet werden! Das gleiche gilt für die veraltete Variabledeklaration mit
var
.
Den Namen, den ihr in JavaScript einer Variable gebt, könnt ihr mit ein paar Einschränkungen frei bestimmen:
- Der Name darf nur Zahlen, Buchstaben und die Symbole
$
und_
enthalten - Der Name darf nicht mit einer Zahl beginnen
- Der Name darf kein reserviertes Schlüsselwort (z.B. if, let, else, usw.) sein
- Der Name darf zwar internationale Zeichen wie Umlaute (äöü) etc. enthalten, aber es wird davon abgeraten
- Da die Syntax von JavaScript an Englisch angelegt ist, macht es auch Sinn euren Variablennamen englische Namen zu geben
Ob ihr Klein- oder Großbuchstaben verwendet macht einen Unterschied:
> let amadeus = 'Teacher';
> console.log(amadeus);
'Teacher'
> console.log(Amadeus); // Fehler weil A statt a
Uncaught ReferenceError: Amadeus is not defined
Da Variablen keine Leerzeichen enthalten dürfen, werden Variablennamen, die aus mehreren Wörtern bestehen meistens im sogenannten „camelCase“ geschrieben, also z. B.:
> let meineHausaufgabe = 'hat mein Hund gefressen';
Außerdem sollte der Name zu Verstehen geben, was in der Variable gespeichert wird:
> let xyz = 'Amadeus'; // dem Computer ist egal, welchen Namen die Variable hat
> let name = 'Amadeus'; // aber für uns als Entwickler macht es Sinn, verständliche Namen zu geben
> let teacherName = 'Amadeus'; // noch besser, weil noch spezifischer
Zusammenfassung: ihr könnt eine Variable mit let
gefolgt von einem Namen
deklarieren und ihr mit =
einen beliebigen Wert zuweisen. Diesen Wert könnt
ihr Verändern, wenn ihr der Variable einen neuen Wert mit =
zuweist.
Konstanten
Konstanten sind spezielle Variablen, deren Wert nach der Deklaration nicht mehr
verändert werden darf. Sie werden statt mit let
mit const
deklariert und
müssen zwingend bei der Deklaration einen Wert übergeben bekommen. Ansonsten
gelten für sie die gleichen Regeln wie für normale Variablen:
> const NAME = 'Amadeus'; // Konstante NAME wird deklariert und bekommt den Wert 'Amadeus'
> console.log(NAME);
'Amadeus'
> NAME = 'Günther'; // Fehler, weil Konstante
Uncaught TypeError: Assignment to constant variable.
Aus Konvention heraus werden die Namen von Konstanten, die vor der Ausführung
des Codes bekannt sind immer in Versalien geschrieben, also NAME
statt
name
. Falls der Name einer solchen Konstante aus mehreren Wörtern besteht,
schreibt man ihn mit dem sogenannten „SCREAMING_SNAKE_CASE“. Konstanten die erst
bei der Ausführung des Programms bekannt werden, z.B. durch Berechnung, werden
allerdings wie normale Variablen geschrieben:
> const PI = 3.14159; // vorher bekannt, daher Versal
> const MEINE_HAUSAUFGABE = 'hat mein Hund gefressen'; // leider auch vorher bekannt
> const vierMalPi = 4 * PI; // vor Ausführung unbekannt, bzw. zur Laufzeit berechnet, daher in camelCase
Wann immer ihr wisst, dass ihr einer Variablen keinen neuen Wert zuweisen werdet, solltet ihr diese als eine Konstante definieren – das ist in den meisten Fällen der Fall.
Geltungsbereich von Variablen und Konstanten
Variablen und Konstanten sind in JavaScript nur innerhalb ihrer jeweiligen „Scope“, also ihrem Geltungsbereich deklariert. Eine neue Scope entsteht immer dann, wenn Code sich zwischen zwei geschweiften Klammern ({…}) befindet (einem neuen Block). Die „oberste“ Scope ist immer die Datei, in der der Code ausgeführt wird. Diese wird auch „globale“ Scope genannt.
Das bedeutet im Umkehrschluss, dass es wichtig ist, wo ihr in eurem Code Variablen definiert:
// variablesTest.js
const TEACHER_NAME = 'Amadeus'; // TEACHER_NAME wird in globalen Scope deklariert
function greet(student) {
const studentName = student.name; // studentName wird in der Scope von 'greet' deklariert
if (studentName === TEACHER_NAME) {
const ERROR_MESSAGE = 'Person cannot greet itself'; // ERROR_MESSAGE wird in der Scope des 'if' deklariert
console.log(ERROR_MESSAGE);
} else {
console.log(`${TEACHER_NAME} sagt: Hi, ${studentName}!`);
}
}
greet({ name: 'Günther', semester: 4 }); // Ausgabe: 'Amadeus sagt: Hi, Günther!'
console.log(studentName); // Fehler: studentName ist nicht außerhalb von 'greet' deklariert
Wird das obige Beispiel ausgeführt, erhalten wir am Ende eine Fehlermeldung. Das
passiert, weil wir versuchen, den Wert der Variablen auszugeben, die wir
innerhalb der Funktion deklariert haben. Trotzdem können wir aber innerhalb der
Funktion auf die Variable TEACHER_NAME
zugreifen. Warum?
In JavaScript können wir stets auf die Variablen zugreifen, die in einer „höheren“ Scope deklariert werden, aber nicht auf diejenigen, die in einer „tieferen“ Scope deklariert werden.
Stellt euch vor, Scopes sind Räume mit einem falschen Spiegel. Von Innen kann man nach Draußen sehen, aber von Draußen nicht nach Innen. Wenn wir jetzt eine Schachtel (Variable) innerhalb des Raumes haben, können wir von außen nicht sehen, was sich in ihr befindet. Wir können aber von innen sehen, was sich in einer Schachtel außerhalb des Raumes befindet.
Aber Amadeus, dann macht es doch Sinn einfach alle Variablen in der globalen Scope zu definieren, oder?
Nein, das ist eine sehr schlechte Idee. Variablen sollten immer nur in der Scope definiert werden, in der sie auch verwendet werden. Somit kann es nicht passieren, dass ihr aus Versehen in zwei völlig verschiedenen Teilen eurer Anwendung den selben Variablennamen verwendet und es dann zu sehr schwer identifizierenden Fehlern kommt:
// usefulFunctions.js
function add(a, b) { // Funktionsargumente werden nur für die Scope der Funktion deklariert
return a + b;
}
function multiply(a, b) { // deshalb können wir hier die selben Namen verwenden
return a * b;
}
function addAndMultiply(a, b, c) {
const result = add(a, b);
return multiply(result, c);
}
function multiplyAndAdd(a, b, c) {
/* wir können result hier problemlos wieder deklarieren, weil das result aus
addAndMultiply in einer tieferen Scope sitzt und somit für unsere Funktion
nicht existiert */
const result = multiply(a, b);
return add(result, c);
}
Man hätte im obigen Beispiel natürlich result
global mit let
deklarieren
können und dann innerhalb der Funktionen den Wert mit einer einfachen Zuweisung
ändern können, aber es wäre nicht logisch das zu tun, denn result
wird ja nur
innerhalb der Funktionen gebraucht. In diesem einfachen Beispiel würden dadurch
zwar noch keine Fehler generiert, aber in einer komplexeren Anwendung könnte es
durchaus passieren, dass eine andere Funktion etwas mit dem globalen result
macht, oder einen bestimmten Wert in dieser Variable erwartet, was zu
unerwarteten Fehlern führt.
Außerdem entfernt die JavaScript-Laufzeitumgebung automatisch Variablen, die nicht mehr gebraucht werden aus dem Arbeitsspeicher eures Computers, aber eine globale Variable muss so lange im Speicher behalten werden, bis das Programm beendet ist, was natürlich auf lange Sicht dazu führen kann, dass die Performance eurer Anwendung abnimmt.
Zusammenfassung: in JavaScript sind Variablen nur innerhalb der Scope sichtbar, in der sie deklariert wurden. Tiefere Scopes können die Variablen von höheren Scopes sehen, aber nicht von tieferen Scopes. Variablen sollten immer nur in der Scope definiert werden, in der sie verwendet werden.
Typen
Wie weiter oben schon angemerkt, ist JavaScript eine dynamisch typisierte
Programmiersprache. Dennoch gibt es auch in JavaScript acht grundlegende Typen.
Jede Variable, die ihr deklariert, wird einem dieser Typen angehören. Ausgeben
könnt ihr euch den Typ mit Hilfe von typeof
.
Jeder dieser Typen hat spezielle Funktionen, die es vereinfachen, mit ihm zu arbeiten.
Hier sind die acht „basic types“ von JavaScript:
Number: Number beinhaltet Zahlen wie Integer (ganze Zahlen) und Floats (Gleitkommazahlen). Auch die besonderen Werte
Infinity
,-Infinity
, undNaN
sind vom Typ „Number“.> typeof 1; 'number' > typeof 1.2; 'number' > 1 / 0 Infinity > const x = 1 / 0; > typeof x; 'number' > 'keine Zahl' / 2; // das Ergebnis kann keine Zahl sein, also wird es NaN (Not a Number) NaN > '2' / 2; // SONDERFALL: JavaScript versucht Zeichenketten in Zahlen zu konvertieren und führt die Rechnung dann aus 1
Der Number-Typ bringt außerdem einige nützliche Funktionen mit sich:
Number(wert)
konvertiert Wert in eine Zahl, z.B.Number('12') === 12
Number.isNaN(zahl)
gibt zurück, ob die übergebene ZahlNaN
istNumber.isFinite(zahl)
gibt zurück, ob die übergebene Zahl endlich istNumber.isInteger(zahl)
gibt zurück, ob die übergebene Zahl eine ganze Zahl istNumber.parseFloat(wert)
undNumber.parseInt(wert, radix)
konvertieren einen Wert in eine Gleitkomma-, bzw. ganze Zahl. Radix bestimmt hier das Zahlensystem des Eingabewerts(2 für Binär, 10 für Dezimal, 16 für Hexadezimal, usw.):> Number.parseInt('15xyz', 10) // funktioniert nur, weil 10 am Anfang der Zeichenkette steht 15 > Number.parseInt('xyz15', 10) NaN > Number.parseInt('0100', 2) // so schreibt man 4 in Binärcode 4 > Number.parseInt('f', 16) // so schreibt man 15 in Hexadezimal 15
Mehr Informationen auf DevDocs.
BigInt: der
Number
-Typ in JavaScript kann technisch bedingt nur Zahlen bis 2⁵³, bzw. 2⁻⁵³ abbilden, wird die Zahl größer oder kleiner als dieser Wert, springt sie zurück auf 0 und verursacht so einen sogenannten Integer- Overflow. In den meisten Fällen erreicht man nie so hohe Zahlen, aber für manche Verwendungszwecke wie Kryptografie kann es vorkommen. Deshalb gibt es in Node, Chrome und Firefox den TypBigInt
. Ich führe ihn hier der Vollständigkeit halber auf, wir werden diesen Typ im Verlauf des Kurses nicht benötigen (und die meisten von uns wahrscheinlich niemals). Für besonders Neugierige unter euch gibt es hier mehr Informationen dazu.String: Zeichenketten, also beliebige Zeichen in einer Reihe von einem oder mehr, werden „Strings“ genannt. In JavaScript müssen diese Zeichenketten mit Anführungszeichen umgeben werden, um von Schlüsselwörtern und Variabelnamen klar abgegrenzt werden zu können. Es ist prinzipiell egal, ob ihr doppelte (""), oder einfache ('') Anführungszeichen verwendet. Im Airbnb- Code-Style werden allerdings immer einfache Anführungszeichen verwendet, außer ihr wollt einfache Anführungszeichen innerhalb des Strings verwenden:
> const aString = 'Ein ganz normaler String'; > const bString = "Ein ganz normaler String"; // doppelte Anführungszeichen gehen auch, sollten aber vermieden werden > aString === bString; // es ist egal, welche Anführungszeichen man benutzt, das Ergebnis ist identisch true > const cString = "Doppelte Anführungszeichen sollten benutzt werden, wenn man im 'String' Einfache benutzt."; > const dString = 'Man kann die Anführungszeichen aber auch \'escapen\', dann kann man auch Einfache benutzen';
Wie ihr in der letzten Zeile des Beispiels sehen könnt, könnt ihr Zeichen auch mit einem vorangestellten Backslash () „escapen“. Das bedeutet, dass die Laufzeitumgebung das Zeichen als das Zeichen selbst und nicht ein Steuerzeichen interpretiert. In unserem Beispiel würde das einfache Anführungszeichen vor „escapen“ eigentlich den String beenden, da aber ein Backslash vorangestellt wurde, weiß die Laufzeitumgebung, dass es einfach nur das Zeichen sein und nicht den String beenden soll. Das bedeutet allerdings auch, dass ihr einen Backslash escapen müsst, wenn ihr ihn in einem String verwenden wollt:
> const unescaped = 'Hier kommt ein Backslash (\)'; // der Backslash hat hier die schließende Klammer escaped und wird dann ignoriert > const escaped = 'Hier kommt ein Backslash (\\)'; // hier sagen wir der Laufzeitumgebung, dass der Backslash einfach nur ein Backslash ist > unescaped === escaped; // die Strings sind nicht gleich false > console.log(unescaped); // wenn wir sie ausgeben, wird klar, warum 'Hier kommt ein Backslash ()' > console.log(escaped); 'Hier kommt ein Backslash (\)'
Ein besonderes Zeichen, dass euch in diesem Kontext öfter begegnen wird ist
\n
. Nach dem gerade gelernten sollte das eigentlich für JavaScript wie ein einfaches „n“ aussehen – allerdings ist\n
ein Sonderzeichen für sich selbst und signalisiert einen Zeilenumbruch. Es gibt mehrere solcher Sonderzeichen in Strings, falls ihr also einmal nicht das Sonderzeichen, sondern zum Beispiel die Zeichen „\“ und „n“ meint, müsst ihr sie escapen:> const unescaped = 'Das ist ein\nZeilenumbruch'; > const escaped = 'Das ist ein\\nZeilenumbruch'; > console.log(unescaped); Das ist ein Zeilenumbruch > console.log(escaped); Das ist ein\nZeilenumbruch > const tab = 'Ein Tabulator wird mit \\t dargestellt: \t tabbed'; // wie gesagt, es gibt noch mehr dieser Zeichen > console.log(tab); Ein Tabulator wird mit \t dargestellt: tabbed
Es wird oft vorkommen, dass ihr die Werte von Variablen innerhalb eines Strings ausgeben möchtet. Natürlich könnt ihr Strings durch Addition aneinander ketten, aber in modernem JavaScript gibt es sogenannte „Template- Literals“, die euch diese Aufgabe vereinfachen. Diese besonderen Strings werden mit Backticks umgeben (`) und können dann innerhalb der besonderen Zeichenfolge
${}
normales JavaScript ausführen:> const likes = 'Wein und Käse'; > const aString = 'Ich mag ' + likes + '.'; > console.log(aString); Ich mag Wein und Käse. > const bString = `Ich mag ${likes}.`; > aString === bString; true > console.log(bString); Ich mag Wein und Käse. > console.log(`Man kann hier sogar Rechnen: ${ 5 + 10 * 3 }!`); Man kann hier sogar Rechnen: 35! // JavaScript wird ausgeführt
Wie auch der Number-Typ, bringt der String-Typ viele nützliche Funktionen mit sich, unter Anderem:
'ein String'.charAt(index)
gibt das Zeichen am angegebenen Index zurück, kann auch als'ein String'[index]
geschrieben werden'ein String'.includes(wert)
gibt zurück, obwert
im String vorhanden ist'ein String'.toUpperCase()
und'ein String'.toLowerCase()
konvertieren den String in Groß-, bzw. Kleinbuchstaben'ein String'.trim()
entfernt „Whitespace“, d.h. Zeichen, die nicht sichtbar sind wie Leerzeichen und Newline-Zeichen vom Anfang und Ende des Strings'ein String'.replace(wert, ersatz)
ersetztwert
im String mitersatz
, z.B.'ein String'.replace('ein', 'ein anderer')
würde'ein anderer String'
ergeben
Boolean: Booleans sind die Wahrheitswerte
true
undfalse
. Diese werden vor allem dazu verwendet, um die Konzepte „ja“ und „nein“ auszudrücken:> 3 > 5 // ist 3 größer als 5? false // nein > 10 <= 20 // ist 10 kleiner gleich 20? true // ja > let ausgeruht = false; > ausgeruht = powernap(); // die Variable ausgeruht erhält den Rückgabewert der ausgedachten powernap-Funktion, in diesem Fall true > console.log(ausgeruht); true > typeof ausgeruht 'boolean'
Mit Hilfe der Funktion
Boolean(wert)
könnt ihr jeden Wert in einen Boolean konvertieren. Beachtet dabei:''
,0
,null
,undefined
werden zufalse
, aber'false'
,1
und so ziemlich jeder andere Wert werden zutrue
. Dieser Umstand wird später in Bedingungen wichtig und kann zu Verwirrung führen.null: in JavaScript bedeutet der
null
-Typ einfach nur, dass etwas nicht existiert, oder unbekannt ist. Eine Besonderheit ist, dasstypeof null
'object'
zurückgibt. Ihr könnt also nur überprüfen, ob eine Variable den Wertnull
enthält, wenn ihr sie mit dem strikten Vergleichsoperator===
mitnull
vergleicht:> const nichts = null; > typeof nichts; 'object' > nichts === null; true
Eine Variable mit dem Wert
null
wird nicht automatisch aus dem Speicher entfernt! Sie existiert einfach weiter mit dem Wertnull
, es macht also keinen Sinn, unbenötigte Variablen aufnull
zu setzen.undefined: ähnlich wie
null
stehtundefined
in JavaScript für „deklariert, aber noch kein Wert zugewiesen“. Theoretisch kannundefined
einer Variable als neuer Wert zugewiesen werden, aber in diesen Fällen ist es bessernull
zu verwenden. Eine Überprüfung vontypeof variableName
auf den Wert'undefined'
macht Sinn, um sicherzugehen, dass eine Variable einen Wert hat, bevor sie weiter verwendet wird:// undefinedTest.js function foo(bar) { if (typeof bar === 'undefined') { bar = 'not passed in'; // wenn bar undefiniert ist, weisen wir einen Wert zu } return `The argument was ${bar}.`; } console.log(foo()); // gibt 'The argument was not passed in.' aus console.log(foo('baz')); // gibt 'The argument was baz.' aus
Object: im Gegensatz zu den anderen Typen, die jeweils nur einen einzigen Wert enthalten können, sind Objekte in der Lage komplexe Datenstrukturen zu beinhalten. Datenstrukturen wie Listen, die von Außen wie ein eigener Typ wirken und es in anderen Programmiersprachen auch oft sind, sind in JavaScript einfach besondere Objekte. Objekte sind hochkomplex und bieten Stoff für mehrere Vorlesungen, aber für unsere Zwecke reicht es aus, wenn ihr sie vorerst als „Sammlungen“ von Variablen seht, wie ein Wörterbuch aus Schlüssel und Wert-Paaren. Diese einfachen Objekte definiert ihr, indem ihr einer Variablen eine Reihe von Schlüssel/Wert Paaren innerhalb von geschweiften Klammern ({}) übergebt:
> const person = { name: 'Gustav', lieblingsfarbe: 'rot', alter: 10, angemeldet: true, pronomen: 'er' }; > console.log(person.name); // Zugriff auf Wert über Schlüssel, z.B. 'name' Gustav > console.log(`Das ist ${person.name}, ${person.pronomen} ist ${person.alter} Jahre alt.`); Das ist Gustav, er ist 10 Jahre alt. > const farbFeld = 'lieblingsfarbe'; > console.log(person[farbFeld]); // man kann auch Schlüssel in Variablen stecken, muss dann aber mit dieser Syntax arbeiten rot > person.age = 11; // Werte können geändert werden > person['favourite food'] = 'Nudelsalat'; // Schlüssel können auch Strings sein und im Nachhinein hinzugefügt werden > delete person.name; // Schlüssel/Wert Paare können mit delete aus einem Objekt gelöscht werden. > console.log(person); { lieblingsfarbe: 'rot', alter: 10, angemeldet: true, pronomen: 'er', age: 11, 'favourite food': 'Nudelsalat' }
Für unsere Zwecke besonders nützlich sind außerdem folgende Funktionen des Object-Typs:
Object.entries(objekt)
gibt uns eine Liste aller Schlüssel/Wert Paare vonobjekt
in der Form[Schlüssel, Wert]
zurück.Object.keys(objekt)
gibt uns eine Liste aller Schlüssel vonobjekt
Object.values(objekt)
gibt uns eine Liste aller Werte vonobjekt
Für eine detailliertere Beschreibung von Objekten, könnt ihr euch diesen Artikel durchlesen – aber ich bin der Meinung, dass ihr euch leichter tun werdet, wenn ihr Objekte organisch über die Praxis kennenlernt.
Symbol: Symbole werden dafür verwendet, Objektinstanzen einzigartige Identifier zuzuweisen und sind nur der Vollständigkeit halber gelistet.
Bonus: Arrays sind besondere Objekte, mit denen ihr so oft zu tun haben werdet, dass man fast meinen könnte es wäre ein grundlegender Typ wie String oder Number. Grob gesagt sind Arrays geordnete Listen – hierbei unterscheiden sie sich wesentlich von Objekten, die generell ungeordnet sind. Auch sind die „Schlüssel“ von Arrays immer Zahlen, der sogenannte „Index“, also die Position des Elements in der Liste.
Ein neues Array definiert ihr indem ihr einer Variablen eine Liste von Werten innerhalb von eckigen Klammern ([]) zuweist:
> const myArray = ['wert', 1, { key: 'value' }, true];
> console.log(myArray[2]);
{ key: 'value' }
Ein Array kann gleichzeitig Werte von allen möglichen Typen beinhalten, auch
weitere Arrays und Objekte. Zugegriffen wird wie bei Objekten mit der Klammer-
Syntax: array[index]
, wobei index
immer eine Zahl zwischen 0 und der Länge
des Arrays minus 1 sein muss.
Achtung:
Wie ihr seht ist der erste Index immer 0, was bedeutet, dass das erste Element im Array den Index 0 hat! Das ist in fast allen Programmiersprachen so und wird euch mit der Zeit als völlig selbstverständlich erscheinen. Eine Begründung dafür findet ihr hier, falls ihr es unbedingt wissen wollt. Ansonsten akzeptiert einfach, dass man beim Programmieren sogut wie immer von 0 aufwärts zählt.
Das erste Element im Array ist also nameDesArrays[0]
. Die Länge eines Arrays
könnt ihr über nameDesArrays.length
ausgeben lassen. Um auf das letzte Element
in einem Array zuzugreifen, ohne eine feste Länge definieren zu müssen, könnt
ihr also einfach nameDesArrays[array.length - 1]
schreiben.
Wie auch schon bei Objekten könnt ihr natürlich den Wert an einem Index beliebig austauschen:
> const myArray = ['one', 'two', 'three'];
> myArray[1] = 'two and a half';
> console.log(myArray);
['one', 'two and a half', 'three']
Theoretisch könnt ihr so auch neue Elemente zum Array hinzufügen, aber es macht mehr Sinn die extra dafür verfügbaren Funktionen zu verwenden:
> const myArray = ['one', 'two', 'three'];
> myArray.push('four'); // fügt den Wert am Ende des Arrays ein
> myArray.unshift('zero'); // fügt den Wert am Anfang des Arrays ein
> console.log(myArray);
['zero', 'one', 'two', 'three', 'four']
> myArray.pop() // entfernt den letzten Wert des Arrays
> console.log(myArray);
['zero', 'one', 'two', 'three']
> myArray.shift() // entfernt den ersten Wert des Arrays
> console.log(myArray);
['one', 'two', 'three']
> myArray.splice(2, 0, 'two and a half'); // fügt einen Wert am angegebenen Index ein
> console.log(myArray);
[ 'one', 'two', 'two and a half', 'three' ]
> myArray.splice(2, 1); // löscht den Wert am angegebenen Index
> console.log(myArray);
[ 'one', 'two', 'three' ]
Wichtig dabei ist, dass es deutlich schneller ist, push()
und pop()
zu
verwenden, als shift()
und unshift()
. splice()
ist eine besonders komplexe
Funktion, die ihr nur dann verwenden solltet, wenn es nicht anders möglich ist.
Was genau sie alles kann, findet ihr hier.
Andere nützliche Funktionen für Arrays sind:
nameDesArrays.forEach()
um eine Funktion für jeden Wert des Arrays aufzurufennameDesArrays.sort()
zum Sortieren des ArraysArray.isArray(nameDesArrays)
zum überprüfen, obnameDesArrays
auch wirklich ein Array ist (datypeof nameDesArrays === 'object'
)nameDesArrays.indexOf(wert)
um den Index vonwert
zu findennameDesArrays.join(zeichen)
um das Array zu einem String mit dem Trennzeichenzeichen
zu konvertieren
Zusammenfassung: es gibt 8 basis-Typen in JavaScript, aber wir werden meistens nur mit String, Number, Boolean, null, und Object arbeiten. Arrays sind spezielle Objekte, die sich hervorragend dafür eignen, sortierte Listen zu repräsentieren. Alle Typen und Arrays haben spezielle Funktionen, die uns das Leben erleichtern.
Funktionen
Mehrfach ist nun schon der Begriff Funktion gefallen, den manche von euch vielleicht auch als Methode kennen. Wie Variablen seid ihr damit auch schon im Mathematikunterricht in Berührung gekommen, z.B. in der Form eines Polynoms f(x) = x² – Funktionen in JavaScript funktionieren ähnlich, ihr schreibt eine Reihe von Anweisungen, die einen Input verarbeiten und dadurch einen Output generieren. Im Endeffekt also ein Programm im Programm und tatsächlich sind Funktionen auch die Bausteine eurer Programme.
Ihr größter Nutzen ist es, euren Quellcode zu vereinfachen und Wiederholungen zu vermeiden. Wir werden später einen Algorithmus zum mischen eines Kartenstapels entwickeln und ihr könntet diesen Algorithmus natürlich jedes mal neu abtippen, wenn ihr einen Kartenstapel mischen müsst, aber es ist viel einfacher und weniger Fehleranfällig, diesen Algorithmus in eine Funktion zu stecken, die man dann einfach immer wieder aufrufen kann.
Es gibt mehrere Möglichkeiten in JavaScript eine Funktion zu definieren, aber wir werden uns nur mit den beiden häufigsten Formen auseinander setzen: der „function declaration“ und der „Arrow-Function“, aber ungleich der Definitionsform sind alle Funktionen gleich aufgebaut:
// functionTest.js
// einfache Function Declaration
function sayHello() {
console.log('Hello World!');
}
// das selbe als Arrow-Function, nützlich für Einzeiler
const sayHelloArrow = () => console.log('Hello World!');
// einmal definiert können sie beliebig oft aufgerufen werden
sayHello();
sayHello();
sayHelloArrow();
sayHelloArrow();
Um den Output einer Funktion zu kontrollieren kann man ihr einen Input übergeben. Diesen Input, der jegliche Form von Daten sein kann, nennt man „Argumente“ oder „Parameter“:
// greet.js
// Function Declaration mit Argumenten
// name und vorname sind Variablen und verhalten sich als wären sie mit 'let' deklariert
function greet(name, vorname) {
console.log(`Hello ${vorname} ${name}!`);
}
// Als Arrow-Function
const greetArrow = (name, vorname) => console.log(`Hello ${vorname} ${name}!`);
// beim Aufrufen der Funktionen können dann Werte übergeben werden
greet(); // name und vorname sind undefiniert, also Ausgabe: 'Hello undefined undefined!'
greet('Amadeus'); // name ist 'Amadeus', vorname ist undefiniert, also Ausgabe: 'Hello undefined Amadeus!'
greetArrow('Günther', 'Jauch'); // name ist 'Günther', vorname ist 'Jauch', also Ausgabe: 'Hello Jauch Günther!'
greetArrow('Gans', 'Gustav'); // name ist 'Gans', vorname ist 'Gustav', also Ausgabe: 'Hello Gustav Gans!'
greet(2, 1); // Argumente können jeder mögliche Typ sein, Ausgabe: 'Hello 1 2!'
Da Funktionsargumente sich verhalten als wären sie mit let
innerhalb der
Funktion deklariert sind sie immer vom Typ undefined
wenn beim Aufrufen der
Funktion kein Argument übergeben wird. Um das zu verhindern kann man ihnen einen
„Default“-Wert geben:
// greetWithDefault.js
// Arrow-Funktionen können auch Mehrzeilig sein
const greet = (name = 'Unbekannt', vorname = 'Anonymous') => {
console.log(`Hello ${vorname} ${name}!`);
}
greet(); // keine Argumente übergeben, default wird benutzt, Ausgabe: 'Hello Anonymous Unbekannt!'
/* um für das erste Argument den default zu nutzen, muss es als undefined
übergeben werden, wenn man ein weiteres Argument angeben will */
greet(undefined, 'Amadeus'); // Ausgabe: 'Hello Amadeus Unbekannt!'
Standardmäßig geben Funktionen undefined
zurück. Für das Beispiel oben:
// greetWithDefault.js
// …Rest der Datei…
const greetResult = greet();
console.log(typeof greetResult); // Ausgabe: 'undefined'
Um einen anderen Wert zurückzugeben, wird das return
-Schlüsselwort verwendet:
// add.js
function add(a, b) {
return a + b; // gib die Summe von a und b zurück
}
const result = add(1, 1);
console.log(result); // Ausgabe: 2
console.log(add(5, 5)); // Ausgabe: 10
/* Besonderheit bei einzeiligen Arrow-Functions: das Ergebnis des Statements
direkt nach dem Arrow (=>) wird zurückgegeben */
let multiply = (a, b) => a * b; // wie als stünde vor a * b 'return': => return a * b
console.log(multiply(2, 2)); // Ausgabe: 4
function returnTest(a, b) {
return;
let result = a + b; // wird nie ausgeführt, denn return beendet die Funktion
}
Achtung:
Falls sich innerhalb der Funktion Code nach dem return
befindet,
wird dieser ignoriert, denn die Funktion endet immer beim return
.
Das bedeutet auch, dass man die Ausführung einer Funktion mit return
frühzeitig beenden kann, z.B. wenn ein Argument nicht vom richtigen Typ ist.
Wie Variablen könnt ihr Funktionen frei benennen. Allerdings gelten auch hier
die selben Einschränkungen und Empfehlungen wie für Variabelnamen. Außerdem
solltet ihr immer das Prinzip der Single Responsibility im Kopf behalten, also
immer nur eine Aktion pro Funktion ausführen. Es macht keinen Sinn, wenn die
Funktion login(username, password)
auch die Funktion ist, mit der ein Account
erstellt wird, dafür bietet sich eine separate signup(username, password)
Funktion an und vielleicht eine dritte Funktion, die überprüft, ob das Passwort
alle erforderlichen Kriterien erfüllt. Schreibt lieber mehrere kleine Funktionen
als eine Große!
Zusammenfassung: Funktionen sind die Bausteine eurer Programme. Sie
verarbeiten Argumente und können dann Daten mit return
zurückgeben. return
beendet die Ausführung der Funktion sofort und ignoriert Code danach. Funktionen
werden entweder über function
oder mit dem Arrow =>
definiert und können
danach beliebig oft aufgerufen werden, indem zwei runde Klammern (()) an den
Funktionsnamen gehängt werden, zwischen die die Argumente gesetzt werden:
meineFunktion(argument1, argument2)
.
Schleifen (Loops)
Wir wissen jetzt, dass wir Code, den wir mehrfach brauchen in Funktionen stecken und beliebig oft aufrufen können. Aber eine Funktion hundert Mal von Hand aufzurufen ist noch immer nicht besonders effizient. Deshalb gibt es Schleifen, die uns die Arbeit wieder leichter machen.
In JavaScript gibt es zwei Arten von Schleifen: for
und while
.
While-Schleifen werden ausgeführt, so lang eine Bedingung true
ist. Es ist
die einfachste Form einer Schleife:
// läuft, bis euer Computer schmilzt – schlechte Idee
while(true) {
console.log('Hello World!');
}
// besser:
let counter = 0;
while (counter < 10) { // nur true so lang counter kleiner als 10 ist
console.log('Hello World!');
counter += 1; // 1 zum Counter hinzufügen, identisch zu counter = counter + 1
}
Jeden Durchlauf einer Schleife nennt man eine Iteration. Der erste Loop in
obigem Beispiel führt (theoretisch) unendlich Iterationen aus. Der Zweite exakt
10. While-Loops können, wie auch For-Schleifen, mit break
beendet werden, auch
wenn ihre Bedingung theoretisch noch true
ist.
For-Schleifen sind etwas komplizierter als While-Loops, werden aber deutlich häufiger verwendet. Im Prinzip kann man jeden For-Loop auch als While-Schleife schreiben, aber es ist oft übersichtlicher und weniger Fehleranfällig, wenn man einfach einen For-Loop benutzt:
// loopTest.js
const myArray = ['one', 'two', 'three'];
// Implementierung im While-Loop
let i = 0; // i ist eine konventionelle Abkürzung für 'iteration' und 'index'
while (i < myArray.length) {
console.log(myArray[i]);
i += 1;
}
// das gleiche als For-Loop
for (let i = 0; i < myArray.length; i += 1) {
console.log(myArray[i]);
}
For-Schleifen geben uns die Möglichkeit, direkt eine Zähler-Variable zu
definieren, die automatisch nach jeder Iteration verändert wird (meistens
mit i += 1
um 1 erhöht). Die Variable kann natürlich heißen, wie ihr wollt,
es wird nur aus Konvention heraus i
verwendet. Und auch die Bedingung, die
zwischen der Deklaration der Zähler-Variable und dem Statement zu ihrer
Veränderung führt, kann theoretisch alles mögliche sein, hängt aber in den
meisten Fällen mit dem Wert der Zähler-Variablen zusammen.
for (let zählerVariable = wert; bedingung; veränderungDerVariable) {
// Code der ausgeführt werden soll
// zählerVariable ist hier verfügbar und hat den aktuellen Wert
}
Wie ihr seht eignen sich For-Loops perfekt, um mit jedem Element eines Arrays etwas zu machen.
Aber Amadeus, es gibt doch Array.forEach()! Das ist viel besser!
Natürlich gibt es Array.forEach()
, aber nur mit einem For-Loop hast du die
Möglichkeit diesen vorzeitig abzubrechen, oder Elemente zu überspringen:
// breakExample.js
const numbers = [1, 2, 3, 3, 4];
let sum = 0;
for (let i = 0; i < numbers.length; i += 1) {
const currentNumber = numbers[i];
const lastNumber = numbers[i - 1];
if (typeof currentNumber !== 'number') { // wenn die aktuelle Nummer keine Zahl ist
console.log('Invalid Number Detected!'); // gib einen Fehler aus
sum = 0; // setze sum zurück
break; // brich den Loop ab
}
// wenn die letzte Zahl gleich ist wie die Aktuelle, überspringe die Iteration
if (currentNumber === lastNumber) continue;
sum += currentNumber; // ansonsten addiere die aktuelle Zahl zur Summe
}
console.log(sum); // Ausgabe: 10
// wäre numbers === [1, '2', 3]
// wäre die Ausgabe 'Invalid Number Detected' und 0
Achtung:
continue
-Statements sollten eigentlich wann immer möglich durch
Conditionals ersetzt werden, da continue
den Code oftmals
weniger gut verständlich macht. Eslint mit der Airbnb-Konvention wird euch
warnen, falls ihr ein continue
Statement verwendet.
Das selbe Ergebnis ließe sich auch mit Array.forEach()
erreichen, allerdings
würden bei einer Liste von 100 Zahlen, bei der die erste ein String ist,
trotzdem 100 Iterationen stattfinden, weil Array.forEach()
nicht mit break
abgebrochen werden kann. In einem For-Loop gäbe es nur eine einzige Iteration.
Außerdem kann man mit einem For-Loop auch zum Beispiel über Strings iterieren,
die keine forEach()
-Methode haben.
Zusammenfassung: um sich wiederholenden Code, also zum Beispiel den Aufruf
einer Funktion für jedes Element in einer Liste, möglichst einfach zu schreiben
verwendet man Schleifen (Loops). Es gibt einen while
-Loop für einfache
Situationen und einen viel häufiger verwendeten for
-Loop.
Bedingungen
Inzwischen habt ihr in vielen Beispielen schon gesehen, dass manchmal ein oder
mehrere Statements nur dann ausgeführt werden sollen, wenn bestimmte Bedingungen
eintreffen. Wie in den meisten anderen Programmiersprachen auch, wird das in
JavaScript über if…else
-Blöcke geregelt, sogenannte „Conditionals“.
Ihr könnt euch diese wie Wenn…Dann-Sätze vorstellen. Wenn das Statement
innerhalb der Klammern nach dem if
true
zurückgibt, wird der Code innerhalb
der geschweiften Klammern danach ausgeführt. Ansonsten wird, falls vorhanden,
der Code im else
-Block ausgeführt:
// conditionals.js
let weather = 'sonnig';
if (weather === 'sonnig') { // wenn weather gleich 'sonnig'
console.log('Du brauchst keinen Regenschirm.'); // dann dass
} else if (weather === 'windig') { // wenn das erste if nicht zutrifft, wird diese Bedingung überprüft
console.log('Es ist nicht sonnig, aber es ist zu windig für einen Regenschirm');
} else { // falls kein if oder else if zutrifft, wird dieser Code ausgeführt
console.log('Du solltest einen Regenschirm mitnehmen.');
}
// Ausgabe: 'Du brauchst keinen Regenschirm'
/* da jeweils nur ein statement in den jeweiligen Blöcken steht, könnte man es
auch so schreiben: */
weather = 'schnee';
if (weather === 'sonnig') console.log('Du brauchst keinen Regenschirm.'); // dann dass
else if (weather === 'windig') console.log('Es ist nicht sonnig, aber es ist zu windig für einen Regenschirm');
else console.log('Du solltest einen Regenschirm mitnehmen.');
// Ausgabe: 'Du solltest einen Regenschirm mitnehmen'
Wie ihr seht, könnt ihr mit else if
beliebig viele Bedingungen stellen, bevor
der else
-Block ausgeführt wird. Manchmal ist es aber sinnvoller in diesen
Fällen ein switch
-Statement zu verwenden, über das ihr hier
mehr lernen könnt.
Tipp:
Sowohl while
und for
-Schleifen, wie auch
if
, else if
und else
- Blöcke lassen
sich einzeilig schreiben, wenn sie nur ein Statement beinhalten. Ich empfehle
diese Schreibweise aber nur dann, wenn die Zeilen dadurch nicht zu lang werden
und die Lesbarkeit nicht leidet.
Die eigentlichen Bedingungen, die ihr in die Klammern nach dem if
schreibt
werden immer so interpretiert, dass am Ende true
oder false
herauskommt und
der Code im if
-Block wird nur dann ausgeführt, wenn true
das Ergebnis ist.
Das bedeutet, dass if(0)
nicht ausgeführt wird, weil Boolean(0) === false
,
passt also auf, wenn ihr mit Zahlen arbeitet!
Um diese Bedingungen zu schreiben gibt es eine Reihe an Vergleichsoperatoren:
===
(ist zu 100% gleich): strikter Vergleich, trifft nur zu wenn Wert und Typ identisch sind, z.B:> 1 === 1 true > '1' === 1 false
Dieser Vergleich sollte dem normalen Vergleich (
==
) immer vorgezogen werden, außer man weiß, was man tut, dafalse == 0
→true
, aberfalse === 0
→false
.!==
(ist zu 100% ungleich): strikter Ungleichheits-Vergleich. Das Gegenteil von===
<
(ist kleiner): wie in der Mathematik, z.B.5 < 10
→true
,10 < 5
→false
>
(ist größer): wie in der Mathematik, z.B.5 > 10
→false
,10 > 5
→true
<=
(ist kleiner oder gleich), z.B.5 <= 10
→true
,10 <= 10
→true
=>
(ist größer oder gleich), z.B.5 >= 10
→false
,10 >= 10
→true
Wollt ihr mehrere Bedingungen verknüpfen, gibt es auch logische Operatoren:
&&
(und): nurtrue
wenn beide Bedingungen zutreffen||
(oder):true
wenn eine Bedingung zutrifft!
(nicht): negiert den Wert, z.B.!true === false
→true
Kombiniert ergibt das folgendes:
// multipleConditions.js
const number = 4;
// number ist größer 0 UND number ist kleiner gleich 4 ODER der ganzzahlige Rest von number / 2 ist 0
if ((number > 0 && number <= 4) || number % 2 === 0) { // % ist der Modulo-Operator, er gibt immer den Rest einer Division zurück
console.log('Die Zahl ist gerade, oder kleiner gleich 4 und größer als 0');
}
/* Achtung:
(number > 0 && number <= 4) || number % 2 === 0 ist etwas Anderes als
number > 0 && (number <= 4 || number % 2 === 0)
*/
Achtung:
Die Verknüpfung mehrerer logischer Operatoren kann sehr schnell sehr verwirrend werden. Benutzt deshalb immer Klammern um Elemente zusammenzusetzen, die zusammen gehören. Wie in der Mathematik werden Klammern von innen nach außen aufgelöst. Auch hier wird euch euer Linter helfen indem er euch warnt.
Wenn ihr euch Code von anderen Entwicklern anseht, werdet ihr manchmal über folgendes stolpern, zum Beispiel, wenn einer Variable ein Wert anhand einer Bedingung zugewiesen wird, oder eine Arrow-Function kurz gehalten werden soll:
> const stars = 4;
> const cheese = stars === 5 ? 'yummy' : 'gross';
> const rateCheese = (rating) => rating !== 5 ? console.log('You savage!') : console.log('You know what’s up!');
> rateCheese(3);
You savage!
> rateCheese(5);
You know what’s up!
In manchen Fällen macht das Sinn, aber vor allem für den Anfang werdet ihr euch
leichter tun euren Code im Nachhinein zu verstehen, wenn ihr die if…else
-
Statements ausschreibt:
// sameAsAbove.js
const stars = 4;
let cheese;
if (stars === 5) cheese = 'yummy';
else cheese = 'gross';
const rateCheese = (rating) => {
if (rating !== 5) console.log('You savage!');
else console.log('You know what’s up!');
}
rateCheese(3); // Ausgabe: You savage!
rateCheese(5); // Ausgabe: You know what’s up!
Zusammenfassung: Wenn…Dann-Sätze könnt ihr in JavaScript mit if…else Blöcken
beschreiben. if
-Blöcke werden nur ausgeführt, wenn die Bedingung in den
Klammern true
ist. else
-Blöcke sind optional. In seltenen Fällen werden
Conditionals auch mit ?
geschrieben, aber das sollte nur von erfahreneren
Entwicklern verwendet werden.
Jetzt kennt ihr die Grundkonzepte von JavaScript und des Programmierens allgemein. Aber wie wendet man diese nun an, um ein Programm zu entwickeln? Damit geht es jetzt weiter.
Wie entwickelt man ein Programm?
Ein Programm zu entwickeln ist eigentlich einfach. Ihr müsst nur genau wissen, was das Programm machen soll und welche Daten es dafür braucht. Dann entwickelt ihr einen Algorithmus (oder sucht nach einem Bestehenden) und übersetzt diesen in die Programmiersprache eurer Wahl.
Dann gebt ihr die benötigten Daten ein und das Programm gibt euch den programmierten Output zurück. Easy peasy! 😉
Die Sache mit dem Kartenstapel
Nehmen wir als Beispiel das Mischen eines Kartenstapels. Wenn wir mit unserem ungemischten Kartenstapel zu einer x-beliebigen Person gehen und diese bitten, ihn zu mischen sagen Manche vielleicht „Ich kann nicht mischen“ und breiten den Stapel vor sich auf dem Tisch aus und schieben die Karten hin und her, Andere nehmen den Stapel selbstbewusst an sich und mischen ihn mit den ausgefallensten Tricks, und wieder Andere mischen ihn einfach, ohne groß darüber nachzudenken. Tatsache ist, dass wir am Ende auf die eine oder andere Art und Weise einen mehr oder weniger gemischten Kartenstapel zurückbekommen werden.
Geben wir die selbe Aufgabe einem Computer, wird er sofort anfangen mit Fehlermeldungen um sich zu werfen:
- Was ist ein Kartenstapel?
- Was ist eine Karte?
- Was heißt denn bitte „mischen“?
- Was willst du von mir?
Natürlich werden die Fehlermeldungen nicht so aussehen, aber ihr versteht hoffentlich, worauf ich hinauswill: der Computer hat keine Ahnung davon, was ein Kartenstapel ist, und weiß auch nicht, was „mischen“ sein soll. Es ist, als hätten wir einem Kleinkind unseren Stapel gegeben (wobei das Kleinkind es wahrscheinlich trotzdem irgendwie schaffen würde, den Stapel zu mischen 😉).
Der Computer hat nur ein sehr, sehr grundlegendes Verständnis von der Welt, wie wir sie kennen, bzw. haben schon andere, viel begabtere Entwickler als wir Konstrukte geschaffen, um dem Computer dieses Verständnis beizubringen – die sogenannten Programmiersprachen. Ein Computer kann zum Beispiel gut zwischen „Wahr“ und „Falsch“ unterscheiden. Ein Computer hat auch ein Konzept von Listen (Arrays) und Objekten (Sammlungen von Daten). Ein Computer weiß auch, dass es Dinge gibt, die man Ausführen kann: Funktionen.
Unsere Aufgabe ist es jetzt unser Beispiel mit dem Kartenstapel in diese sehr rudimentären Begriffe zu übersetzen und dem Computer Schritt für Schritt zu erklären wie man einen Kartenstapel mischt. Diese Anleitung nennt man einen Algorithmus.
Aber Amadeus, das ist doch total umständlich, ich mische meinen Kartenstapel lieber selbst, dann bin ich viel schneller fertig!
Das stimmt natürlich, aber was, wenn du 50 Kartenstapel mischen musst? Oder 100? Der gewaltige Vorteil eines Computers ist, dass er einen guten Algorithmus in einer für einen Menschen unerreichbaren Geschwindigkeit ausführen kann. Natürlich sind nicht alle Algorithmen gleich gut, aber selbst mit einem mittelmäßigen Misch-Algorithmus hätte der Computer 100 Kartenstapel gemischt, bevor du deinen überhaupt in die Hand genommen hättest.
Einen Algorithmus könnt ihr euch wie ein Kochrezept vorstellen: ihr gebt dem Computer eine Liste an Zutaten, eine genaue Anleitung, was mit den Zutaten zu machen ist und der Computer gibt euch am Ende das Ergebnis des Rezepts zurück.
Wie entwickelt man also einen solchen Algorithmus? Nehmen wir unser Beispiel. Es geht nur darum die Karten zu mischen, also ist es für den Computer nicht wichtig, was genau eine Karte ist. Wir könnten sie also einfach als eine Zahl repräsentieren, oder als ein Objekt, dass den Kartennamen hält, oder eine Zeichenkette, die die Karte beschreibt – wie immer gibt es unendlich viele Möglichkeiten. Wir entscheiden uns in diesem Fall für ein Objekt (auch wenn eine Zahl effizienter wäre). Ein Stapel Karten ist einfach eine Liste von Objekten mit einer bestimmten Reihenfolge, dafür bietet sich also ein Array an.
Jetzt weiß der Computer also, was wir mit einem Stapel Karten meinen. Der nächste Schritt ist es, dem Computer zu erklären, was „mischen“ bedeutet und dafür können wir uns einfach ansehen, wie die meisten Menschen mischen:
Sie nehmen den Stapel in eine Hand, nehmen sich eine zufällige Zahl von Karten vom Anfang des Stapels, aber niemals alle Karten, denn das würde keinen Sinn machen, und legen diesen kleineren Stapel einfach auf das Ende des verbleibenden Stapels. Diesen Vorgang wiederholen sie beliebig oft, aber natürlich nicht unendlich oft und auch nicht nur ein oder zwei Mal, denn dann wäre der Stapel ja nicht wirklich gemischt.
Natürlich gibt es auch Menschen, die den Stapel auf einem Tisch ausbreiten und die Karten hin und her schieben und dann wieder zu einem Stapel formen, oder Menschen, die den Stapel in kleinere Stapel aufteilen und diese dann ineinander Schieben, sodass sich praktisch immer eine Karte aus einem Stapel über eine Karte des anderen Stapels mischt, aber am Ende kommt immer ein gemischter Stapel dabei heraus. Genauso gibt es viele verschiedene Algorithmen, um das gewünschte Ergebnis zu erreichen, wir müssen uns nur für einen entscheiden.
Ein konkreter Algorithmus für unser Beispiel nach der ersten Mischmethode:
Zutaten:
- Ein Array aus Kartenobjekten
Anweisungen:
- Nimm eine zufällige Anzahl an Kartenobjekten, die geringer als die Gesamtanzahl von Karten ist, vom Anfang des Arrays und stecke sie ans Ende des Arrays
- Wiederhole diesen Vorgang eine zufällige Anzahl von Malen, aber mindestens fünf und maximal 25 mal
- Gib das veränderte Array zurück
Dieser Algorithmus ist sicher nicht der Effizienteste oder Beste, aber für den Anfang funktioniert er. Wie man auf Englisch so schön sagt: it gets the job done. Optimieren und verbessern kann man im Nachhinein noch immer. Wichtig ist, dass der Algorithmus deterministisch ist, also dass er bei gleichem Input immer der gleiche Output generiert wird (bei Zufallszahlen ist das natürlich schwierig, aber unser Algorithmus ist dennoch deterministisch, denn wenn wir die Zufallszahlen auf feste Werte setzen, werden wir am Ende immer die selbe „gemischte“ Reihenfolge erhalten).
Hat man erst einmal einen Algorithmus, muss man diesen nur noch in eine Programmiersprache übersetzen und man hat ein fertiges Programm, das ein Computer ausführen kann (in diesem Fall eine Funktion, damit wir sie immer wieder verwenden können):
function shuffleDeck() {
// Ein Array aus Kartenobjekten
let cards = [{ name: 'König' }, { name: 'Dame' }, { name: 'Bube' }, { name: 'Ass' }];
// Eine zufällige Zahl zwischen 5 und 25
const iterations = Math.floor(Math.random() * 21) + 5 // Math.random() gibt eine zufällige Zahl zwischen 0 und 1 aus, z.B. 0.2134
// eine Schleife, die so lange ausgeführ wird, wie die Variable i kleiner ist als die variable iterations
for (let i = 0; i < iterations; i += 1) {
const cardsToGrab = Math.floor(Math.random() * (cards.length - 1)) + 1; // Zufällige Zahl, die immer 1 kleiner ist als die Gesamtanzahl Karten
cards = cards.concat(cards.splice(0, cardsToGrab));
}
// gibt die gemischten Karten zurück
return cards;
}
Für viele Probleme haben andere Menschen schon Algorithmen gefunden, die nahezu Perfekt sind, deshalb wird es recht selten vorkommen, dass ihr einen Algorithmus neu erfinden müsst. Für unser obiges Problem könntet ihr also einfach Google fragen und würdet wahrscheinlich recht schnell auf den Fisher-Yates shuffle stoßen. Grob zusammengefasst geht dieser Alogrithmus rückwärts durch den Kartenstapel und tauscht immer die „aktuelle“ Karte mit einer zufällig ausgewählten Karte, die vorher im Stapel auftaucht:
function fisherYatesShuffle() {
// Ein Array aus Kartenobjekten
const cards = [{ name: 'König' }, { name: 'Dame' }, { name: 'Bube' }, { name: 'Ass' }];
// Schleife, die rückwärts durch das Array geht
for (let currentIndex = cards.length - 1; currentIndex > 0; currentIndex -= 1) {
// Zufälliger index zwischen 0 (erste Karte) und dem aktuellen Index
const randomCardIndex = Math.floor(Math.random() * (currentIndex + 1));
// Aktuelle Karte
const currentCard = cards[currentIndex];
// Tausche die Karten
cards[currentIndex] = cards[randomCardIndex];
cards[randomCardIndex] = currentCard;
}
// gibt die gemischten Karten zurück
return cards;
}
Dieser Algorithmus ist schwieriger zu verstehen als unser Einfacher von zuvor, allerdings in einigen Bereichen deutlich überlegen:
- Er hat eine deutlich größere Zufälligkeit, d.h. die Wahrscheinlichkeit mit der selben Reihenfolge zu enden, mit der man angefangen hat, ist kleiner
- Er funktioniert bei allen möglichen Kartenstapeln, während unser Algorithmus Schwierigkeiten bei kleinen und großen Kartenstapeln hat, weil die Qualität des gemischten Stapels von der Anzahl der Karten und der Anzahl der Iterationen abhängt, die Karten werden also in den meisten Fällen nicht besonders gut gemischt
- Er ist deutlich effizienter, denn mit unserer Lösung müssen wir unsere
Schleife mindestens 5 und maximal 25 Mal ausführen, dieser Algorithmus führt
die Schleife immer genau so oft aus, wie Karten im Stapel sind (mal abgesehen
davon, dass die Funktionen
Array.splice()
undArray.concat()
aufwendiger auszuführen sind als ein einfacher Tausch)
Wie ihr seht lohnt es sich also, erst einmal zu recherchieren, wie man etwas macht, bevor man seine eigene Lösung entwickelt. Glaubt mir, richtige Entwickler verbringen Stunden auf Seiten wie StackOverflow. 😉
Trotzdem kommt es natürlich immer wieder vor, dass ihr vor einem Problem steht, das entweder noch nicht gelöst wurde, oder das so spezifisch ist, dass ihr keine allgemeingültigen Lösungen dafür finden könnt. Dann müsst ihr eure eigenen Lösungen entwickeln und solltet dabei darauf achten die einfachste Lösung zu wählen, die das Problem für euch löst. Optimieren kann man nacher immer noch, wenn es zu Performance-Problemen kommen sollte, aber zunächst ist es wichtig, dass eure Lösung überhaupt funktioniert.
Überlegt euch, wie ihr als Mensch das Problem lösen würdet und übersetzt das in eine für den Computer verständliche Form.
Wie geht man mit Fehlermeldungen um?
Irren ist menschlich, hat ein alter Römer einmal gesagt. Egal wie gut ihr programmieren könnt, ihr werdet immer Fehler machen – und das ist okay. Man kann viel von seinen Fehlern lernen, wie wir ja schon besprochen haben, als es um Linter ging.
Wie viele andere Programmiersprachen auch, versucht JavaScript euch möglichst nützliche Fehlermeldungen auszugeben:
/Pfad/zur/Datei/breakExample.js:6
for (let i = 0; i < Numbers.length; i += 1)
^
ReferenceError: Numbers is not defined
at Object.<anonymous> (/home/amadeus/Git/hm-webdesign/content/examples/breakExample.js:6:21)
In diesem Beispiel habe ich mich vertippt und statt numbers
Numbers
geschrieben. Beim Ausführen der Datei mit node Pfad/zur/Datei/breakExample.js
hat ist Node auf den Fehler gestoßen, hat die Ausführung des Programms
abgebrochen und mir die Fehlermeldung ausgegeben.
Diese Fehlermeldung besteht aus mehreren Teilen:
- Der Angabe der Datei und der Zeile in der Datei in der der Fehler auftrat
- Wo genau in der Zeile der Fehler auftrat
- Was für ein Fehler aufgetreten ist, in diesem Fall ein ReferenceError, da „Numbers“ nicht deklariert wurde
- Eine sogenannte Stack-Trace, also der Aufruf welcher Funktionen letztendlich zum Auftreten ds Fehlers geführt hat. In diesem konkreten Fall war es nur die Datei selbst, aber später einmal werden diese Traces länger werden, wenn ihr zum Beispiel mit Libraries arbeitet. In speziellen Fällen kann das nützlich sein, aber für euch relevanter sind die ersten drei Punkte und natürlich vor allem die Fehlermeldung selbst, denn die könnt ihr googeln, falls ihr sie nicht versteht. 😉
Wie ich bereits angemerkt habe, bricht JavaScript das komplette Programm ab
sobald ein Fehler auftritt. Das ist natürlich nicht immer ideal, weshalb ihr für
bestimmte Situationen try…catch
-Blöcke verwenden könnt, um die Fehlermeldungen
„abzufangen“. Das solltet ihr allerdings erst dann tun, wenn ihr wisst, dass
euer Code einwandfrei funktioniert und die Fehlermeldungen deshalb auftreten
können, weil evtl. keine Internetverbindung besteht oder Ähnliches, das
außerhalb eurer Kontrolle liegt.
Wir werden uns weiter mit Fehlerbehandlung beschäftigen, wenn wir über Datenbanken sprechen, aber falls ihr jetzt schon neugierig seid, könnt ihr hier mehr darüber erfahren.
Was ist eine Dokumentation und wie liest man die?
Bei solch komplexen Systemen wie Programmiersprachen, aber auch Bibliotheken, Frameworks und Libraries für eine Programmiersprache wie JavaScript ist es unabdingbar, dass neue und bestehende Entwickler einen guten Ein- und Überblick über alle Details erlangen können. Zu diesem Zweck existieren die sogenannten Dokumentationen, die erklären, wie eine Sprache, Library, etc. verwendet werden kann, wie sie funktioniert und welche Funktionen sie dem Programmierer an die Hand gibt.
Wenn ihr schon etwas mehr mit DevDocs gearbeitet habt, habt ihr bereits gesehen, wie diese Dokumentationen aufgebaut sind. Ihr könnt einfach nach einem Modul, oder einer Funktion suchen und erhaltet sofort alle relevanten Informationen.
So könnt ihr zum Beispiel in DevDocs oder direkt im MDN
nach Math
suchen, dem Modul für mathematische Funktionen in JavaScript und
werdet schnell erkennen können, welche Funktionen es in diesem Modul gibt und
wie ihr sie verwenden könnt.
Dokumentationen sind neben dem Quellcode von Anwendungen die beste Möglichkeit für euch zu lernen, aber es macht natürlich keinen Sinn, eine solche Dokumentation von Vorne bis Hinten durchzulesen, viele der Funktionen braucht ihr nur selten oder gar nicht und es würde euch nur verwirren.
Gute Frameworks und Libraries wie Vue geben euch deshalb sogenannte „Guides“ an die Hand, die euch Schritt für Schritt an die Library heranführen, sowie eine API-Dokumentation, in der ihr schnell eine bestimmte Funktion nachschlagen könnt.
Bei kleineren Projekten, Modulen, oder Libraries findet ihr diese Informationen meistens einfach in der README.md Datei im jeweiligen Git-Repository.
Es macht Sinn, sich durch diese Guides zu arbeiten, wenn man eine Library das erste Mal verwendet, oder für ein Projekt evaluiert. Ansonsten empfehle ich euch, nur die Teile der Dokumentation zu lesen, die für euch gerade relevant sind.
Wenn ihr zum Beispiel die Aufgabe habt, in JavaScript eine zufällige Zahl zu generieren und ihr nicht wisst wie, sollte eure erste Anlaufstelle Google sein – bitte behaltet dabei im Kopf, dass Programmieren auf Englisch geschieht und deshalb fast alles auf Englisch ist, ihr also auch auf Englisch googeln solltet.
Google wird euch höchstwahrscheinlich die Funktion Math.random
ausspucken, und
wahrscheinlich auch eine Anleitung und ein Tutorial, wie sie zu benutzen ist.
Für später ist das super, aber jetzt wo ihr noch lernt ist es besser, wenn ihr
die Anleitungen ignoriert, DevDocs aufmacht und euch dort die Dokumentation zu
Math.random
durchlest und dann selbst versucht, euer Problem mit diesen
Informationen zu lösen. Dadurch erzielt ihr einen größeren Lerneffekt und wie
wir gelernt haben: Verbessern kann man später immer noch.
Tipp:
Anstatt Google könnt ihr natürlich gerne auch immer mich fragen, aber Google wird euch wahrscheinlich schneller ein Ergebnis liefern. 😉 Außerdem weiß ich auch nicht alles auswendig – das muss auch niemand. Fragt also für einfache Fragestellungen eher Google, dann erspart ihr es mir für euch googeln zu müssen und ich habe mehr Zeit, euch die Dinge konkreter und tiefergehender zu erklären, falls es einmal Verständnisschwierigkeiten geben sollte, oder ihr einfach mehr zu einem Thema erfahren wollt. 😊
Praxis
Wir haben heute sehr viele Beispiele und Konzepte besprochen. Jetzt ist es wichtig, dass ihr all dieses neue Wissen ausprobieren könnt, damit ihr es verinnerlicht. Viele dieser Konzepte sind schließlich recht abstrakt und man kann sie nur dann wirklich verstehen, wenn man sie selbst benutzt.
In diesem Sinne möchte ich, dass ihr erst einmal durch die Beispiele von heute geht und sie einfach mal selbst schreibt und versucht genau zu verstehen, was passiert. Besonders wichtig ist dabei meiner Meinung nach, dass ihr die Code- Zeilen selbst abtippt und nicht einfach kopiert und wieder einfügt, auch wenn die Versuchung dafür groß sein mag. Viele der heute besprochenen Statements werdet ihr noch viele weitere Male brauchen, deshalb macht es Sinn auch eure Finger schon einmal daran zu gewöhnen, sie zu tippen.
Für kurze Kommandos und einzelne Statements empfehle ich euch den interaktiven
Modus von Node, den ihr einfach mit der Eingabe node
und Enter
erreichen könnt. Für mehrzeilige Programme, kleine Algorithmen, mehrere
Statements, etc. empfehle ich euch, *.js
Dateien anzulegen. Diese könnt ihr
ausführen, indem ihr node pfad/zur/datei.js
eingebt.
Falls etwas unklar sein sollte, fragt einfach nach.
Das Zahlenratespiel
Um das heute gelernte Wissen weiter zu vertiefen, möchte ich, dass ihr ein kleines Zahlenratespiel entwickelt. Hierbei soll sich der Computer eine zufällige Zahl in einem vorgegebenen Rahmen „ausdenken“, die ihr dann über möglichst wenige Eingaben erraten sollt. Wann immer ihr eine Zahl eingebt, die nicht die „ausgedachte“ Zahl ist, gibt der Computer euch einen Tipp, ob die Zahl größer oder kleiner ist, als die von euch eingegebene.
Hier ein paar Hilfestellungen dazu:
Math.random()
gibt euch eine zufällige Zahl zwischen 0 und 1, wobei sie niemals 1 ist (1 exklusiv). Ein Aufruf vonMath.random()
gibt euch also zum Beispiel 0.7947725165815829 zurückDer folgende Code erlaubt es euch, eine Benutzereingabe im Terminal anzufordern und zu verarbeiten:
// importiere die 'readline'-Library und initialisiere sie const input = require('readline').createInterface({ input: process.stdin, // input und output werden vom Terminal eingelesen output: process.stdout, prompt: 'Your answer: ', // das Prefix, das angezeigt wird, wenn input.prompt() ausgeführt wird }); // definiere einen Event Listener, also eine Funktion, die immer dann // aufgerufen wird, wenn das „line“-Event auftritt. // Das passiert immer dann, wenn nach input.prompt() Enter gedrückt wird. // „answer“ beinhaltet den eingegebenen Text ohne das Prefix. input.on('line', (answer) => { // do something // z.B. console.log(answer) }); // mit input.close() könnt ihr das Programm wieder beenden, ansonsten die // Ausführung im Terminal mit Ctrl+C abbrechen. Das passiert, weil das // readline-Interface in der input-Variablen solange auf Input wartet, bis // es mit .close() geschlossen wird input.prompt(); // jetzt wird im Terminal 'Your answer: ' ausgegeben und auf eine Eingabe gewartet
Da er recht komplex ist, ist es für den Augenblick nicht wichtig, wie genau er funktioniert. Alles was ihr wissen müsst ist, dass der Aufruf
input.prompt()
die Zeile'Your answer: '
ausgibt und dann darauf gewartet wird, dass eine Eingabe erfolgt, die mit Enter bestätigt wird. Dann wird die Funktion aufgerufen, die ihr ininput.on('line')
definiert hat. Diese Funktion bekommt den eingegebenen Text als Argument übergeben und ihr könnt ihn dann weiterverarbeiten.Überlegt euch einen geeigneten Algorithmus, bevor ihr darauf los programmiert. Dann wird es euch leichter fallen den eigentlichen Code zu schreiben.
Die Aufgabe ist nicht die Einfachste für den Anfang, aber ich stehe euch gerne für Fragen und weitere Hilfestellungen zur Verfügung.
Weitere Gestaltung
Ihr solltet inzwischen einige Screens eurer ToDo-Anwendung ausgestaltet haben. Wenn ihr für diese gerne Feedback hättet, lasst es mich wissen. Wenn ihr in Figma gearbeitet habt, dann fügt mich zu eurer Datei hinzu, damit ich dort direkt Kommentare hinterlassen kann. Ansonsten könnt ihr mir aber natürlich auch gerne einfach Bilder zukommen lassen, die ich dann als Basis für mein Feedback benutzen werde.
Falls ihr es noch nicht getan habt, ist nun auch ein guter Zeitpunkt, sich über die Responsivität der App Gedanken zu machen. Überlegt euch, wie sie auf verschiedenen Bildschirmen aussehen kann und gestaltet eine Desktop-Version aus.
In der nächsten Session werden wir mehr über JavaScript an sich lernen und uns ansehen, wie JavaScript und HTML/CSS zusammengehören und miteinander interagieren können. 🎉