JavaScript-Tutorium:Grundlagen: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
Kowa (Diskussion | Beiträge)
Zeile 1.050: Zeile 1.050:
var f1 = function() {
var f1 = function() {
   console.log('called f1');
   console.log('called f1');
}
};
var f2 = function() {
var f2 = function() {
   console.log('called f2');
   console.log('called f2');
}
};
setTimeout(f1, 500);
setTimeout(f1, 500);
f2();
f2();
</source>
</source>
===Unterschied zwischen Funktionsobjekten und Funktionsaufrufen===
Um den Unterschied zwischen Funktionsobjekt und Funktionsaufruf zu verdeutlichen,
sollte man für die zuvor definierten Funktionen <code>f1</code> und <code>f2</code>
nochmals aufrufen, wobei man das Funktionsobjekt <code>f1</code> durch den Funktionsaufruf <code>f1()</code> ersetzt:
<source lang="javascript">
setTimeout(f1(), 500);
f2();
</source>
Noch deutlicher wird der Unterschied, wenn man <code>f1</code> und <code>f2</code> folgendermaßen definiert:
<source lang="javascript">
var f1 = function() {
  console.log('called f1');
  return function() { console.log('called f2'); };
};
var f2 = function() {
  console.log('called f2');
};
</source>
und dann die beiden Befehlssequenzen
<source lang="javascript">
setTimeout(f1, 500);
f2();
</source>
und
<source lang="javascript">
setTimeout(f1(), 500);
f2();
</source>
vergleicht.

Version vom 16. Oktober 2014, 19:03 Uhr

Ziel

Ziel dieses Tutoriums ist es die Grundlagen und Besonderheiten der Sprache JavaScript zu vermitteln. Zur Veranschaulichung sind viele Code-Beispiele gegeben.


Überblick

Geschichtliches

  • Erfunden von Brendan Eich bei Netscape
  • Adaptiert von Microsoft und JScript benannt
  • Netscape und Microsoft entwickeln unterschiedliche Implementierung
  • Überreicht von Netscape an ECMA und standardisiert als ECMAScript

Spracheigenschaften

  • Syntax ist an C angelehnt
  • Hat fast nichts zu tun mit Java
  • Ist interpretiert, nicht kompiliert
  • Looses Typsystem
  • Objektorientiert, nicht klassorientiert
  • Begünstigt funktionales Programmieren
  • Läuft in vielen unterschiedlichen Umgebungen

Umgebungen

  • Browser, z.B. Firefox, Chrome, Internet Explorer, Safari, Opera
  • Server, z.B. NodeJS, Windows Scripting Host, Rhino
  • Applikationen, z.B. Adobe Photoshop, Quartz Composer
  • Betriebssysteme, z.B. GNOME

JavaScript-Engines

  • SpiderMonkey/JägerMonkey: Firefox, GNOME
  • V8: Chrome, NodeJS
  • Trident/Chakra: Internet Explorer

ECMAScript Versionen

  • ES3 ist vollständig implementiert von allen gängigen Browsern
  • ES5 ist vollständig implementiert von IE9+, FF4+, Chrome7+
  • ES.next/Harmony ist von keinem Browser vollständig implementiert


Schlüsselwörter

Folgende Wörter sind für die Sprache selbst reserviert und können nicht als Variablennamen verwendet werden:

abstract  
boolean break byte  
case catch char class const continue  
debugger default delete do double  
else enum export extends  
false final finally float for function  
goto  
if implements import in instanceof int interface  
long  
native new null  
package private protected public  
return  
short static super switch synchronized  
this throw throws transient true try typeof  
var volatile void  
while with

Die Auswahl der Begriffe orientierte sich stark an der Sprache Java. Einige der Begriffe finden in JavaScript aktuell keine Verwendung.


Variablen

In JavaScript wird das Schlüsselwort var verwendet um Variablen zu deklarieren (und zu initialisieren).

Definition und Zuweisung

Variablen können unmittelbar bei der Deklaration definiert werden.

var tageProWoche = 7;
console.log(tageProWoche);

Oder auch zu späteren Zeitpunkten.

var tageProWoche;
tageProWoche = 7;
console.log(tageProWoche);

Eine Typabgabe ist nicht notwendig da es sich nicht um eine stark typisierte Sprachen handelt. Die Engine kümmert sich automatisch um die Anforderung und Freigabe von Speicher.

var pruefungBestanden = 1;
pruefungBestanden = true;
console.log(pruefungBestanden);

Wie auch in anderen Sprachen sind Mehrfachzuweisungen erlaubt.

var tick, trick, track;
tick = trick = track = 'Ente';
console.log(tick);
console.log(trick);
console.log(track);

Eine erneute Deklaration einer bereits bestehenden Variable führt zu keinem Fehler, sollte allerdings vermieden werden:

var doppeltDeklariert = false;
var doppeltDeklariert = true;
console.log(doppeltDeklariert);

Hoisting

Hoisting bedeutet dass der JavaScript-Interpreter alle Variablendefinitionen vor der eigentlichen Ausführung des Codes innerhalb der umgebenden Funktion nach oben an den Anfang schiebt. Dies betrifft ausschließlich die Definitionen, nicht aber die initiale Belegung mit einem Wert.

Augenscheinlich kann man dadurch Code schreiben der Variablen verwendet bevor sie überhaupt definiert sind. Da jedoch die initiale Wertzuweisung nicht vom Interpreter verschoben wird kann dies zu unerwarteten Resultaten führen.

console.log(nochNichtDeklariert);
var nochNichtDeklariert = 5;

Tipp: Um Hoisting weitesgehend zu vermeiden ist es empfehlenswert alle Variablendeklaration immer am Anfang der jewilig umgebenden Funktion zu schreiben. Handelt es sich um globale Variable sollten diese am Anfang der JavaScript-Datei stehen.


Globale Variablen

In der Regel besitzt eine JavaScript-Umgebung ein globales Objekt, welches alle globalen Properties beherbergt. Im Browser wird das globale Objekt über die Variable window verfügbar gemacht. In Node.JS heisst es global. Falls eine Variable nicht innerhalb einer Funktion definiert wird, wird diese automatisch an das globale Objekt angehängt.

var person1 = new Object();
console.log('ist person1 global?', person1 === window.person1);
var erstellePerson2 = function() {
  var person2 = new Object();
  console.log('is person2 global?', person2 === window.person2);
}
erstellePerson2();

Achtung: Wird das Schlüsselwort var vergessen, wird die Variable implizit als globale Variable definiert.

var erstelleGlobaleVariable = function() {
  globaleVariable = 1;
}
erstelleGlobaleVariable();
console.log(globaleVariable);
console.log(window.globaleVariable);


Primitive Datentypen

JavaScript stellt folgende primitive Datentypen zur Verfügung:

Null, Undefined, Number, Boolean, String

Anmerkung: Number, Boolean und String auch als Objekte behandelt mittels des sogenannten Boxings.


Null

Null ist ein Datentyp der nur einen definierten Wert annehmen kann: null. Anders als in anderen Sprachen ist null nicht der Standardwert einer nicht initialisierten Variable.

var referenz = null;
console.log(referenz);


Undefined

Undefined ist ein Datentyp der ebenso nur einen definerten Wert annehmen kann: undefined. Undefined ist der Standardwert einer nicht initialisierten Variable. Er ist nicht gleichbedeutend mit null.

var referenz1, referenz2 = null;
console.log(referenz1);
console.log(referenz1 === referenz2);

Anmerkung: Die JavaScript-Umgebung stellt eine Variable undefined zur Verfügung welche den Wert undefined enthält. Unglücklicherweise kann diese Variable in manchen JavaScript-Umgebung neu definiert werden.


Boolean

Boolean ist ein boolscher Datentyp welcher die Werte true und false annehmen kann.

var istGrasGruen = true, sindBananenBlau = false;
console.log(istGrasGruen);
console.log(sindBananenBlau);
console.log(istGrasGruen === sindBananenBlau);


Number

Number ist eine 64 bit breite Fließkommazahl, ähnlich zu double wie in Java.

var monateProJahr = 12, pi = 3.14;
console.log(monateProJahr);
console.log(pi);

JavaScript besitzt keinen separaten Datentyp für Integer-Zahlen. Integer-Werte werden auch als Number abgebildet.

var ganzzahl = 1, gleitkomma = 1.0000;
console.log(ganzzahl === gleitkomma);

Es existieren die gängigen arithmetischen Operatoren für Berechnungen.

console.log(1 + 2);
console.log(1 - 2);
console.log(1 * 2);
console.log(1 / 2);

console.log(--1);
console.log(++1);

Anmerkung: Da es sich bei jeder Zahl um eine Fließkommazahl handelt gibt es keine ganzzahlige Teilung. Diese muss über einen Umweg mithilfe der Number API emuliert werden.

var quotient = Math.floor(5 / 3);
console.log(quotient);

Der Wert NaN (not a number) ist das Ergebnis einer mathematischen Operation die nicht in einer Zahl resultiert.

var ungueltigeOperation = 100 / 'a';
console.log(ungueltigeOperation);

Zahlen vom Typ Number können auch in oktaler und hexadezimaler Form geschrieben werden.

var oktal = 017, hexadezimal = 0x10;
console.log(oktal);
console.log(hexadezimal);


Number API

Der Datentyp Number stellt diverse Hilfsfunktionen zur Verfügung.

console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);

var zahl = 2;
console.log(zahl.toFixed(3));

Eine komplette Referenz der String API finden sie auf [dieser](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number/) MDN-Seite.


String

Der Datentyp String wird verwendet um unveränderliche Folgen von 16 bit breiten Zeichen darzustellen. Anders als in anderen Sprachen existiert kein Datentyp für einzelne Zeichen.

Strings können sowohl von einfachen als auch von doppelten Anführungszeichen umgeben sein.

var name1 = 'Paul', name2 = 'Peter';
console.log(name1);
console.log(name2);
console.log(name1 === name2);

Um Anführungszeichen selbst in Strings zu verwenden müssen diese mit vorangestellten Backslash versehen werden.

console.log('\'');
console.log("\"");

Strings können mithilfe des Plus-Operators zu einem neuen String zusammengesetzt werden.

console.log('Max' + ' ' + 'Mustermann');

String API

Ebenso wie der Datentyp Number stellt String unterschiedliche Hilfsfunktionen zur Verfügung

var name = 'Max Mustermann';
console.log('Name hat eine Länge von ' + name.length);
console.log('Name als Großbuchstaben: ' + name.toUpperCase());
console.log('Name als Kleinbuchstaben: ' + name.toLowerCase());
console.log('Name erstes "e" ist an Position ' + name.indexOf('e'));

Eine komplette Referenz der String API finden sie auf [dieser](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/) MDN-Seite.


Vergleiche

Gleichheit (==)

Der Gleichheitsoperator konvertiert Datentypen der beiden zu vergleichenden Ausdrücke und vergleicht dann die Werte. Der Ungleichheitsoperator verhält sich analog.

Die Umwandlung bei Typungleichheit richtet sich nach den folgenden fünf Regeln:

 1. null und undefined sind gleich
 2. Strings werden in Zahlen konvertiert wenn sie mit Zahlen verglichen werden
 3. Boolsche Werte werden in Zahlen konvertiert
 4. Objekte werden konvertiert mittles valueOf() wenn Sie mit Number vergleichen werden 
 5. Objekte werden konvertiert mittels toString() wenn Sie mit String vergleichen werden 
console.log(null == undefined);
console.log('3' == 3);
console.log(true == 1);
console.log(new Object() == '[object Object]');


Anmerkung: Diese Regeln gelten lediglich für die Typumwandlung. Es ist trotzdem ohne weiteres möglich</source>

den Gleichheitsoperator zu verwenden um zum Beispiel Objektreferenzen zu vergleichen.

var person1 = {}, person2 = {};
console.log(person1 == person1);
console.log(person1 == person2);


Identität

Der Identitätsoperator vergleicht zuerst die Datentypen und bei Typgleichheit die Werte zweier Ausdrücke.

console.log(null === undefined);
console.log('3' === 3);
console.log(true === 1);
console.log(new Object() === '[object Object]');


Anmerkung: Die Verwendung des Gleichheitsoperators wird in großen Teilen der JavaScript-Gemeinde als schlechte Programmierpraxis angesehen. Oftmals wird ausschließlich der Identitätsoperator verwendet.


Größer und kleiner

Die Vergleichsoperatoren größer (gleich) und kleiner (gleich) arbeiten auf die gleiche Weise wie der Gleichheitsoperator.

console.log(2 > '1');
console.log(true > 1);


Kontrollanweisung

if-Anweisung

Die Syntax der if-Anweisung ist identisch zu den meisten anderen Sprachen. Es gilt aber zu beachten dass folgende Ausdrücke als false interpretiert werden: false, null, undefined, , 0, NaN

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

if (!null) {
  console.log('!null ergibt true');
  console.log('allerdings ist null nicht gleich false: ' + (null == false));
}

if (!undefined) {
  console.log('!undefined ergibt true');
  console.log('allerdings ist undefined nicht gleich false: ' + (undefined == false));
}

if (!'') {
  console.log('!"" ergibt true');
}

if (!NaN) {
  console.log('!NaN ergibt true');
}


Boolsche Logik

Die Boolsche Logik funktioniert ebenfalls wie in anderen Sprachen, inklusive Short-Circuit.

if (false || null || undefined || '' || NaN || true) {
  console.log(false || null || undefined || "" || NaN || true);
}

var nichtVeraenderteVariable = 1;
if (false && nichtVeraenderteVariable++) {
  
}
console.log(nichtVeraenderteVariable);

Der ternäre Operator interpretiert die gleichen Werte wie das if-Statement als false:

console.log(true ? 1 : 0);
console.log(null ? 1 : 0);
console.log(null != false ? 1 : 0);


switch-Anweisung

Die switch-Anweisung vergleicht einen gegebenen Ausdruck mit einer Liste von Werten (per Identität). Stimmt die Identität eines Wertes mit dem Ausdruck überein so wird der nachfolgend stehende Code ausgeführt. Eine break-Anweisung beendet in solch einem Fall die Code-Ausführung frühzeitig.

var countdown = 3;
switch (countdown) {
  case 3:
    console.log('3');
  case 2:
    console.log('2');
  case 1:
    console.log('1');
    break;
  default:
    console.log('0');
}


while- und do-while-Schleifen

Die while-Schleife führt ein Stück Code so lange wie aus wie ein gegebener Ausdruck den Wert true ergibt. Ergibt der Ausdruck beim ersten Mal bereits nicht true so wird der gegebene Code nie ausgeführt.

var zaehler = 10;
while (zaehler--) {
  console.log(zaehler);
}

while (false) {
  console.log('wird nicht ausgegeben');
}


Die do-while-Schleife funktioniert genauso wie die while-Schleife mit der Ausnahme dass der gegebene auf jeden Fall einmal ausgeführt wird.

do {
  console.log('wird ausgegeben');
} while (false);


try, catch und throw

Die Anweisungen try und catch werden verwendet um Exceptions abzufangen und zu behandeln.

try {
  console.log(objekt.eigenschaft.eigenschaft);
}
catch (error) {
  console.log(error);
}

throw wird verwendet um Exceptions zu werfen. Als Argument sollte ein Error-Objekt übergeben werden.

try {
  throw new Error('Custom error');
}
catch (error) {
  console.log(error);
}


for- und for-in-Schleifen

for-Schleifen funktionieren ebenfalls wie in den meisten anderen Sprachen.

Der for-in-Loop wird verwendet um über Eigenschaften eines Objekts zu iterieren.

Anmerkung: for in sollte in der Regel mit hasOwnProperty() verwendet werden

var spieler = {name: 'Max Mustermann', leben: 3, punkte: 2000};
for (var eigenschaft in spieler) {
  console.log(spieler);
}


Objekte

Jeder Wert mit einem anderen Datentypen als ein primitiver Datentyp ist ein Objekt. JavaScript bietet eingebaute/native Objekte wie Funktionen, Arrays, reguläre Ausdrücke, Daten und generische Objekte. Objekte sind veränderliche Sammlungen von Eigenschaften. Eine Eigenschaft ist ein Schlüssel-Wert-Paar, wobei der Schlüssel ein String und der Wert von beliebigem Typ ist. Objekte sind vergleichbar mit Hash-Maps und/oder Dictionaries.

Variablen speichern immer die Referenz zu Objekten, nicht die Objekte selbst.


Erstellung von Objekten

Die einfachste Art ein Objekt zu erstellen ist mittels des Objektliterals {}.

var person1 = {};
console.log(person1);

Eine weitere Möglichkeit ist der Objekt-Konstruktor, welche allerdings keine Vorteile gegenüber dem Literal bietet.

var person1 = new Object();
console.log(person1);

Objekte können bei ihrer Erstellung mit Eigenschaften und Werten vorbelegt werden. Die Eigenschaften werden als kommaseparierte Liste angegeben. Eigenschaft und Wert werden jeweils durch einen Doppelpunkt getrennt.

var person1 = {name: 'Max Mustermann', alter: 42, hatBrille: true};
console.log(person1);

Eigenschaften müssen in Anführungszeichen angegeben werden falls es sich um reservierte Wörter handelt oder der Name der Eigenschaft Zeichen enthält die nicht bei Variablennamen vorkommen dürfen.

var bier = {'export': true, 'ist alkoholfrei': false};
console.log(bier);

Eigenschaften können sowohl mit Werten primitiver Datentypen als auch mit Objekten und Funktionen belegt werden.

var person1 = {
  vorname: 'Max',
  nachname: 'Mustermann',
  getName: function() {
    return this.vorname + ' ' + this.nachname;
  },
};
console.log(person1);
console.log(person1.getName());

Lesen von Eigenschaften

Es gibt verschiedene Arten um auf die Eigenschaften eines Objekts zuzugreifen.

Zum einen kann die Punktnotation verwendet werden:

var person1 = {name: 'Max Mustermann'};
console.log(person1.name);

Zum anderen ist es möglich eines String-Ausdruck in eckigen Klammern anzugeben:

var person1 = {name: 'Max Mustermann'};
console.log(person1['person1']);

In der Regel wird die erste Variante verwendet wenn der Name der Eigenschaft bekannt und statisch ist. Die zweite Variante wird vor allem verwendet wenn der Eigenschaftsname dynamisch zur Laufzeit bestimmt wird

var person1 = {vorname: 'Max', nachname: 'Mustermann'};
var hatObjektEigenschaft = function(objekt, eigenschaft) {
  return objekt[eigenschaft] ? true : false;
}
console.log(hatObjektEigenschaft(person1, 'vorname');
console.log(hatObjektEigenschaft(person1, 'nachname');

Ebenso wie bei der initialen Befüllung muss auch ein String-Ausdruck verwendet werden wenn es sich bei dem Namen der Eigenschaft um ein reserviertes Schlüsselwort handelt oder aber unerlaubte Zeichen für Variablennamen enthalten sind.

var person1 = {'mag viele Leerzeichen': true};
console.log(person1['mag viele Leerzeichen']);

Ein Zugriff auf eine nicht definierte Eigenschaft eines Objekts liefert den Wert undefined zurück.

var person1 = {};
console.log(person1.adresse);


Schreiben von Werten

Analog zum Lesen können Werte von Eigenschaften sowohl mittels Punktnotation als auch mittels String-Ausdruck verändert werden.

var person1 = {alter: 5};
person1.alter += 5;
person1['alter'] += 5;
console.log(person1.alter);

Beim schreibenden Zugriff auf eine nicht existierende Eigenschaft eines Objekts wird diese implizit definiert. Dies bedeutet dass jedem Objekt dynamisch zur Laufzeit jederzeit beliebige Eigenschaften zugewiesen werden können.

var person1 = {};
person1.alter = 42;
person1.name = 'Rainer Zufall';
person1.hatBrille = true;
console.log(person1);


Abfragen von Eigenschaften

Um alle Eigenschaften eines Objekts abzufragen wird der for in-Loop in Kombination mit hasOwnProperty() verwendet.

var person1 = {name: 'Max Mustermann', alter: 42, hatBrille: true};
for (var eigenschaft in person1) {
  if (person1.hasOwnProperty(eigenschaft)) {
    console.log(eigenschaft + ': ' + person1[eigenschaft]);
  }
}


Löschen von Eigenschaften

Eigenschaften können mittels dem delete Operator von Objekten entfernt werden.

var person1 = {hatBrille: true};
delete person1.hatBrille;
console.log(person1);


Anmerkung: Den Wert einer Eigenschaft auf undefined zu setzen ist nicht mit einem delete gleichzusetzen. Die Zuweisung von undefined lässt die Eigenschaft auf dem Objekt bestehen, z.B. relevant für for in-Schleifen.


Arrays

In JavaScript sind Arrays genau genommen Objekte mir listenähnlichem Verhalten. Im Gegensatz zu anderen Sprachen besitzen sie eine dynamische Länge.


Erstellung

Analog zur Objekterstellung ist der einfachste Weg ein Array zu erstellen mittels des Literals [].

var personenregister = [];
console.log(personenregister);

Alternativ kann man ebenfalls einen Konstruktor verwenden und die length Eigenschaft initial definieren.

var personenregister = new Array(10);
console.log(personenregister.length);

Diese Eigenschaft hat allerdings keine Auswirkung auf das tatsächliche Speichervermögen eines Arrays.

Arrays können ebenso initial mit Werten vorbefüllt werden

var personenregister = [{}, {}, {}, {}, {}];
console.log(a);

Da JavaScript generell nicht stark typisiert ist können Arrays Werte mit unterschiedlichen Typen enthalten.

var personenregister = [{name: 'Max Mustermann'}, {vorname: 'John', nachname: 'Doe'}, 'fehlerhafter Datensatz];
console.log(personenregister);

Die bereits erwähnte Eigenschaft length beschreibt die aktuelle Anzahl an Werten im Array.

console.log('array has a length of ' + [1, 2, 3, 4, 5].length);

Multidimensionale Arrays können ebenfalls mit Literalen erzeugt werden.

var ticTacToe = [
  ['O', 'O', 'X'],
  ['O', 'X', 'X'],
  ['O', 'X', 'O']
];


Lesen von Werten

Werte von Arrays können über den numerischen Index abgefragt werden.

var personenregister = [{name: 'Max Mustermann'}, {name: 'John Doe'}];
console.log(personenregister[0]);


Schreiben von Werten

Ebenso können Werte direkt über den Zugriff auf den Index verändert werden.

var personenregister = [{vorname: 'Mäxle', nachname: 'Mustermann'}];
personenregister[0].vorname = 'Max';
console.log(personenregister);

Wird auf einen noch nicht belegten Index zugegriffen, wird das Array entsprechend erweitert. Bisher nicht belegte Stellen werden mit dem Wert undefined belegt.

var personenreigster = [{name: 'Max Mustermann'}, {name: 'John Doe'}];
console.log(personenreigster);
console.log(personenreigster.length);
personenreigster[5] = {name: 'Frau Holle'};
console.log(personenreigster);
console.log(personenreigster.length);

Veränderungen der Eigenschaft length legen die aktuelle Größe eines Arrays fest unabhängig vom Inhalt.

var personenregister = [];
personenregister.length = 10;
console.log(personenregister);

Um Werte ans Ende des Arrays einzufügen wird die push()-Funktion verwendet.

var personenregister = [];
var person1 = {name: 'Max Mustermann'};
personenregister.push(person1);
console.log(personenregister);

Die pop()-Funktion entfernt hingegen das aktuell letzte Element eines Arrays und verringert dessen Größe um 1.

var personenregister = [{name: 'Max Mustermann'}];
var person1 = personenregister.pop();
console.log(personenregister);
console.log(person1);

Die Funktionen unshift() und shift() funktionieren wie push() und pop(), jedoch für den Anfang eines Arrays.

var personenregister = [];
var person1 = {name: 'Max Mustermann'};
personenregister.unshift(person1);
console.log(personenregister);
personenregister.shift();
console.log(personenregister);

Um mehrere Arrays miteinander zu kombinieren wird die concat()-Funktion verwendet.

console.log([1, 2].concat([3, 4]));

Eine komplette Referenz der Array API ist auf [dieser](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/) MDN-Seite zu finden.


Enumerierung

Um alle Elemente eines Arrays abzufragen wird der for-Loop verwendet.

var personenregister = [{name: 'Max Mustermann'}, {name: 'John Doe'}];
for (var index = 0; index < personenregister.length; index++) {
    console.log(index + ': ' + personenregister[index]);
}


Funktionen

Funktionen sind Objekte in JavaScript. Das bedeutet sie haben Eigenschaften und können Variablen zugewiesen werden. Eine Funktion besteht aus dem Schlüsselwort function, einem Namen, einer Liste von Argumenten und einem Funktionskörper.

Ausführung und Erstellung

Funktionen werden ausgeführt indem man sie mit ihrem Namen aufruft und eine Liste an Argumenten übergibt.

console.log('Hello World');
var max = Math.max(42, 23);
console.log(max);

Es gibt zwei Möglichkeiten Funktionen zu erstellen.

Zum einen kann eine sogenannte Funktionsdeklaration verwendet werden. Diese führt dazu dass eine Variable mit demselben Namen der Funktion definiert wird welche auf die Funktion zeigt.

function f() {
  console.log('f was called');
}
f();

Funktionsdeklaration unterliegen dem sogenannten Hoisting.

Hoisting bedeutet dass der JavaScript-Interpreter die Funktionsdeklaration vor der eigentlichen Ausführung des Codes innerhalb der umgebenden Funktion nach oben an den Anfang schiebt.

Das heisst man kann Code schreiben in dem eine Funktion augenscheinlich aufgerufen wird bevor sie definiert ist.

f();
function f() {
  console.log('f was called');
}

Die zweite Möglichkeit eine Funktion erstellen besteht darin einen Funktionsausdruck einer Variable zuzuweisen. Der Funktionsausdruck sieht dabei der Deklaration sehr ähnlich außer dass in der Regel kein Funktionsname vergeben wird.

var f = function() {
  console.log('f was called');
};
f();

Funktionsausdrücke werden besonders dann bevorzugt wenn man die negativen Effekte des Hoisting minimieren möchte.

f();
var f = function() {console.log('f was called');};

Theoretisch kann einem einer Variable zugewiesenen Funktionsausdruck auch eine Funktionsname gegeben werden. Dieser kann von dem Funktionskörper selbst verwendet werden um die Funktion zu referenzieren, z.B. bei Rekursion.

var drawAll = function drawNext() {
  // ...
  drawNext();
}
drawAll();

Anmerkung: Persönlich hatte ich noch nie Verwendung dafür.

Argumente

Funktionsargumente werden definiert als kommaseparierte Liste und werden bei der Ausführung auch als solche übergeben.

var f = function(a, b, c) {
  console.log(a, b, c);
}
f(1, 2, 3);

Jede Funktion in JavaScript kann mit einer beliebigen Anzahl an Argumenten aufgerufen werden.

var f = function(a, b, c) {
  console.log(a, b, c);
}
f();
f(1, 2);
f(1, 2, 3, 4, 5);

Funktionen verfügen über eine lokale Variable namens arguments. Diese ist ein Array-artiges Objekt und enthält alle Argumente die beim Aufruf übergeben wurden.

var f = function() {
  console.log(arguments.length);
}
f(1);
f(1, 2, 3);
f(true, null, undefined);

Außerdem besitzen Funktionen eine Eigenschaft namens length welche die Anzahl an erwarteten Argumenten beschreibt.

var f = function(a, b) {}
console.log(f.length);

Rückgabewert

Funktionen in JavaScript können Rückgabewerte zurückliefern. Ist kein expliziter Rückgabewert gegeben gibt eine Funktion implizit undefined zurück.

var returnUndefined = function() {
}
console.log(returnUndefined());
var returnOne = function() {
  return 1;
}
console.log(returnOne());

Anmerkung: Ein impliziter Rückgabewert von undefined ist nicht gegeben wenn eine Funktion mit new aufgerufen wird.

Es kann sogar zur Laufzeit entschieden werden ob ein Wert zurückgegeben wird.

var f = function() {
  if (arguments.length > 0) {
    return true;
  }
}
console.log(f());
console.log(f(1)));

(anonymer) Funktionsausdruck

(Anonyme) Funktionsausdrücke sind aufgrund ihrer kompakten Schreibweise nützlich wenn es darum geht Funktionen als Argumente zu übergeben oder als Rückgabewerte zu verwenden.

var execute = function(f) {
  f();
}

execute(function() {
  console.log('Hello World');
});

 var create = function() {
  return function() {};
}

console.log(create());

Scope

Der Scope kontrolliert die Sichtbarkeit und Lebensdauer von Variablen und Argumenten. Anders als in anderen Sprachen besitzt JavaScript einen funktionsbasierten Scope.

var f = function() {
  var a = 0;
  if (arguments.length > 0) {
    var a = 0;
    a = arguments.length;
  }
  return a;
};
console.log(f(1, 2, 3));

var f2 = function() {
  var i = 5;
  for (var i = 0; i < 10; i++) {}
  return i;
}
console.log(f2());

Innere Funktionen

Wie bereits erwähnt sind Funktionen Objekte und können auch Variablen zugewiesen werden. Deswegen kann eine Funktion in ihrem Funktionskörper auch weitere lokale Funktion erstellen.

var onePlusTwo = function() {
  var one = function() {
    return 1;
  };
  var two = function() {
    return 2;
  };
  return one() + two();
};
console.log(onePlusTwo());

Callbacks

Im Browser passieren diverse Aktionen asynchron, besonders meist dann wenn Benutzeraktionenen involviert sind. Normalerweise besitzt JavaScript einen einzigen Thread in dem der Code ausgeführt und Benutzereingaben verarbeitet werden. Deswegen ist es wichtig dass die Ausführung von Code den Thread nicht zu lange blockiert.

Sogenannte Callbacks bieten eine einfach Möglichkeit um mit einer möglichen Asynchronität umzugehen. Anstelle eines unmittelbaren Rückgabewerts erwartet eine Funktion eine weitere Funktion als Argument. Dieser Callback wird wiederum aufgerufen sobald die asynchrone Operation abgedschlossen ist.

Der Browser bietet zwei Funktionen um nach einer bestimmten Zeit einen Callback aufzurufen: setTimeout und setInterval. Diese Funktionen tun nichts anderes außer eine bestimmte Dauer zu warten, allerdings passiert dies asynchron. Das bedeutet dass sie den JavaScript-Thread nicht blockieren und der Browser weiterhin Benutzereingaben empfangen kann.

var f1 = function() {
  console.log('called f1');
};
var f2 = function() {
  console.log('called f2');
};
setTimeout(f1, 500);
f2();

Unterschied zwischen Funktionsobjekten und Funktionsaufrufen

Um den Unterschied zwischen Funktionsobjekt und Funktionsaufruf zu verdeutlichen, sollte man für die zuvor definierten Funktionen f1 und f2 nochmals aufrufen, wobei man das Funktionsobjekt f1 durch den Funktionsaufruf f1() ersetzt:

setTimeout(f1(), 500);
f2();

Noch deutlicher wird der Unterschied, wenn man f1 und f2 folgendermaßen definiert:

var f1 = function() {
  console.log('called f1');
  return function() { console.log('called f2'); };
};
var f2 = function() {
  console.log('called f2');
};

und dann die beiden Befehlssequenzen

setTimeout(f1, 500);
f2();

und

setTimeout(f1(), 500);
f2();

vergleicht.