Start / Seminare / JavaScript Grundlagen & Modernes ECMAScript
Modul
Funktionen, Gültigkeitsbereiche und Closures
5 Kapitel in diesem Modul-Video · Laufzeit
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
JavaScript — Funktionen und Closures
0:00 Es gibt eine Faustregel, die in fast jedem Projekt gilt: Wo Code sich wiederholt, fehlt eine Funktion. Sie ist das Werkzeug, mit dem aus einer langen Abfolge von Anweisungen etwas wird, das man lesen, verstehen und wiederverwenden kann. Denn eine Funktion tut mehr, als Code zu bündeln — sie gibt einem Verhalten einen Namen. Und Namen sind das, womit wir denken.
0:22 Dieses Modul ist der Angelpunkt des Kurses: Alles, was danach kommt — funktionale Muster, Asynchronität, Module, Tests — setzt darauf auf.
Funktionen
0:31 Der dritte Tag beginnt mit fünf Kapiteln zu einem einzigen Thema, und das ist Absicht. Zuerst klären wir, was eine Funktion überhaupt ist und welche Begriffe dazugehören. Dann drei Schreibweisen: die klassische Deklaration, der Funktionsausdruck und die kurze Pfeilform. Alle drei begegnen Ihnen in echtem Code, oft in derselben Datei.
0:51 Und zum Schluss das Thema, das viele lange als schwierig empfinden und das eigentlich ganz greifbar ist, wenn man es am richtigen Beispiel sieht: Gültigkeitsbereiche und Closures.
Was eine Funktion ist
1:02 Fangen wir mit dem Begriff und dem zugehörigen Vokabular an. Zwei Wörter werden dabei im Alltag gern vermischt, und die Verwechslung macht Fehlermeldungen schwerer verständlich, als sie sein müssten. Wir trennen sie sauber. Eine Funktion ist ein wiederverwendbarer Codeblock für eine bestimmte Aufgabe. Interessanter sind die beiden Begriffe danach.
1:22 Ein Parameter ist der benannte Platzhalter in der Definition — er steht da, wenn Sie die Funktion schreiben, und ist zu diesem Zeitpunkt leer. Ein Argument ist der konkrete Wert, den jemand beim Aufruf hineingibt. Das Bild dazu: Der Parameter ist das beschriftete Feld auf einem Formular, das Argument das, was jemand hineinschreibt. Warum das wichtig ist?
1:44 Weil Fehlermeldungen diese Begriffe verwenden — und wer sie auseinanderhält, weiß sofort, ob das Problem in der Definition oder im Aufruf liegt. Vier Gründe, und sie bauen aufeinander auf. Der erste ist der offensichtliche: Logik steht an einer Stelle statt verstreut über das Programm. Der zweite ist die praktische Folge daraus, und er ist der wirtschaftlich wichtigste — eine Änderung wirkt an einem Ort, nicht an fünf Kopien, von denen Sie vier vergessen.
2:11 Der dritte ist eine Denkhilfe: Große Probleme zerfallen in benennbare Teilschritte, und Benennen ist der halbe Weg zum Verstehen. Und der vierte deutet auf ein späteres Modul voraus — Testen wird überhaupt erst möglich, wenn es etwas gibt, das man einzeln aufrufen und prüfen kann. Hier sehen Sie alle Begriffe an einem Beispiel. Oben in der Klammer stehen die Parameter, unten beim Aufruf die Argumente — dieselben Positionen, verschiedene Rollen.
2:37 Das Schlüsselwort für die Rückgabe ist der Ausgang der Funktion: Was dort steht, kommt beim Aufrufer an. Und dann der Block ganz unten, der eine Stilfrage aufwirft, die keine ist. Eine Funktion tut etwas, deshalb sollte ihr Name mit einem Verb beginnen. Ein Substantiv als Funktionsname lässt offen, ob etwas gebaut, geholt oder geprüft wird.
2:58 Beim Lesen muss man dann jedes Mal in die Funktion schauen — und genau das wollten wir vermeiden. Nehmen Sie einen Prüfblock, der dreimal kopiert im Code steht, und machen Sie eine Funktion daraus, die dreimal aufgerufen wird — bei unverändertem Ergebnis. Die Pflichtfeldprüfung aus dem Modul zur Eingabeprüfung ist ein guter Ausgangspunkt, weil sie sich dort natürlicherweise wiederholt.
3:21 Achten Sie auf das „unverändert" im Erfolgskriterium. Das ist dieselbe Disziplin wie beim Entflechten der Bedingungen: umbauen, ohne das Verhalten zu ändern. Diese Fähigkeit heißt Refactoring, und sie unterscheidet Entwickler, denen man einen bestehenden Code anvertraut, von solchen, denen man es nicht tut. Der Substantiv-Name führt die Liste an, weil er so verbreitet ist — und weil er die Lesbarkeit im Alltag mehr kostet, als man glaubt.
3:47 Der zweite Punkt ist die natürliche Alterung von Funktionen: Sie wachsen, bis der Name sie nicht mehr beschreibt. Wenn Sie beim Erklären „und dann macht sie außerdem" sagen müssen, ist es Zeit zum Teilen. Punkt drei ist ein Entwurfsfehler mit Folgen: Wer innerhalb der Funktion direkt ausgibt, statt einen Wert zurückzugeben, macht sie untestbar und unbrauchbar für andere Zwecke.
4:08 Und der letzte ist die Begriffsverwechslung, wegen der wir das Vokabular geklärt haben.
Funktionsdeklarationen
4:13 Jetzt die klassische Schreibweise. Sie hat eine Eigenschaft, die es so nur bei ihr gibt und die praktisch bedeutsam ist — man nennt sie Hoisting. Und wir sehen ein Muster wieder, das wir bei den Bedingungen kennengelernt haben. Die Form ist geradlinig: das Schlüsselwort, ein Name, die Parameterliste, der Rumpf. Zwei Sätze verdienen Aufmerksamkeit. Erstens: Der Rumpf läuft erst beim Aufruf.
4:37 Beim Lesen der Datei passiert also nichts — die Funktion wird nur bekannt gemacht. Zweitens das Hoisting: Deklarationen werden im Sinne der Sprache nach oben gezogen und sind schon vor ihrer Zeile im Code nutzbar. Das ist bequem, wenn Sie Hilfsfunktionen ans Ende der Datei schreiben wollen, und es ist eine Falle, wenn man sich darauf verlässt. Wir kommen bei den Stolpersteinen darauf zurück.
5:01 Zwei Mechanismen, die Funktionen flexibel machen. Der Standardwert beim zweiten Parameter springt ein, wenn beim Aufruf nichts kommt — die erste Ausgabe zeigt es. Das ist deutlich eleganter, als im Rumpf zu prüfen, ob ein Wert fehlt. Der Restparameter mit den drei Punkten sammelt alles Weitere in ein Array ein, egal wie viel kommt; die zweite Ausgabe zeigt zwei zusätzliche Werte.
5:24 Das sind dieselben drei Punkte wie beim Spread aus Modul drei, hier in der sammelnden Richtung. Und die Regel gilt auch hier: Der Restparameter muss an letzter Stelle stehen. Anders wäre gar nicht entscheidbar, wo er aufhört. Das ist die Guard-Clause-Idee aus Modul vier, jetzt in Funktionsform — und in Funktionen kommt sie erst richtig zur Geltung.
5:45 Drei Zeilen, drei Fälle, keine Verschachtelung, kein else. Der Grund steht im Fußtext: Sobald die Rückgabe erreicht ist, endet die Funktion sofort. Alles danach läuft nicht mehr. Deshalb braucht die zweite Zeile keine untere Grenze anzugeben — wer bis dorthin gekommen ist, hat die erste Prüfung überstanden. Diese Bauweise ist knapp, flach und liest sich fast wie eine Aufzählung von Regeln. Nur die Reihenfolge müssen Sie im Blick behalten, denn sie trägt die Logik mit.
6:14 Fangen Sie eine optionale Eingabe über einen Standardwert ab, statt sie im Rumpf zu prüfen. Der Nachweis ist elegant formuliert: Der Aufruf ohne zweites Argument muss dasselbe liefern wie der Aufruf mit ausdrücklich gesetztem Standard. Wenn das stimmt, ist der Standardwert wirklich der Standard und nicht bloß ein Notnagel.
6:32 Ergänzen Sie dann noch einen Restparameter und rufen Sie die Funktion mit unterschiedlich vielen Werten auf. Sie sehen dann, wie eine einzige Signatur ganz verschiedene Aufrufformen verträgt — und genau das macht gut entworfene Funktionen angenehm zu benutzen. Sich auf das Hoisting zu verlassen, ist erlaubt und trotzdem keine gute Idee — eine Datei, die von unten nach oben gelesen werden muss, ist eine Zumutung für jeden Nachfolger.
6:57 Der Restparameter an falscher Stelle ist schlicht ein Syntaxfehler und damit harmlos. Nach einer Rückgabe weiteren Code zu erwarten, ist ein Denkfehler, der bei den Guard-Clause-Ketten schnell passiert. Und der letzte Punkt verdient Nachdenken: Ein Standardwert, der einen fehlenden Wert stillschweigend verdeckt, kann schlimmer sein als ein Fehler.
7:16 Manchmal soll es eben knallen, wenn eine Pflichtangabe fehlt.
Funktionsausdrücke
7:20 Jetzt kommt eine Einsicht, die den Blick auf die Sprache verändert: Funktionen sind Werte. Man kann sie in Variablen legen, weitergeben und zurückgeben wie eine Zahl. Und dabei fällt zum ersten Mal ein Begriff, der uns bis in die asynchronen Module begleiten wird. Ein Funktionsausdruck erzeugt eine Funktion als Wert und weist sie einer Variablen zu. Das klingt nach einer technischen Nuance und ist eine grundlegende Eigenschaft der Sprache.
7:46 Eine Funktion ist hier nichts Besonderes — sie ist ein Wert wie eine Zahl oder ein Text. Also lässt sie sich weitergeben, aus einer anderen Funktion zurückgeben und später aufrufen. Und weil sie ein ganz normaler Wert ist, folgt sie auch den ganz normalen Regeln für Variablen: const oder let, mit allem, was daraus folgt.
8:05 Genau das ist der Unterschied zur Deklaration. Die dritte Zeile ist die interessante: Eine Funktion wird einer zweiten Variablen zugewiesen — ohne Klammern, also ohne sie aufzurufen. Danach zeigen beide Namen auf dieselbe Funktion, und beide lassen sich aufrufen. Das ist dieselbe Referenz-Semantik, die wir bei Objekten gesehen haben, denn eine Funktion ist in JavaScript ein Objekt. Der untere Block bringt den neuen Begriff.
8:30 Die kleine Funktion, die Sie an die Filtermethode übergeben, heißt Callback — eine Funktion, die Sie nicht selbst aufrufen, sondern die von jemand anderem aufgerufen wird. Mit genau diesem Muster arbeiten wir seit Modul sechs, ohne es benannt zu haben. Das Prinzip hinter der Tabelle: Die Deklaration ist ein Sonderfall, der Ausdruck folgt den normalen Regeln.
8:52 Gehoistet heißt, die Deklaration ist im ganzen Bereich verfügbar, auch oberhalb ihrer Zeile. Der Ausdruck dagegen existiert erst ab seiner Zuweisung, genau wie jede andere Variable. Die dritte Zeile ist die Beruhigung: Herauskommt in beiden Fällen eine ganz normale Funktion, die sich hinterher identisch verhält. Der Unterschied betrifft nur die Frage, ab wann sie benutzbar ist. Und der Fußtext trifft es — auffallen wird Ihnen das erst, wenn ein Aufruf zu früh im Ablauf steht.
9:21 Übergeben Sie dieselbe Funktion an zwei verschiedene Aufrufe und lassen Sie sie jeweils passende Ausgaben erzeugen. Damit behandeln Sie eine Funktion praktisch als das, was sie ist: ein Wert. Der Hinweis ist der wichtigste Satz der Folie, weil dieser Fehler wirklich jedem passiert: Übergeben Sie die Funktion ohne Klammern.
9:40 Mit Klammern rufen Sie sie sofort auf und übergeben ihr Ergebnis — was meistens undefined ist. Der Code läuft dann, tut aber nichts, und die Fehlersuche führt an ganz andere Stellen. Merken Sie sich: Klammern bedeuten „jetzt ausführen". Die Klammern beim Übergeben sind, wie gesagt, der Klassiker — und besonders unangenehm, weil kein Fehler entsteht.
10:01 Der zweite Punkt ist die praktische Folge aus der Tabelle: Ein Funktionsausdruck vor seiner Zeile aufgerufen führt zu einem echten Fehler, weil die Variable noch nicht belegt ist. Der dritte ist die falsche Erwartung dahinter — Ausdrücke sind nicht gehoistet, auch wenn sie eine Funktion enthalten. Und der letzte ist eine Lesbarkeitsfrage, die mit steigender Erfahrung wichtiger wird: Ein Callback, der über zwanzig Zeilen geht, macht den umgebenden Aufruf unlesbar.
10:27 Dann geben Sie ihm einen Namen und übergeben nur diesen.
Pfeilfunktionen
10:30 Jetzt die Schreibweise, die modernen JavaScript-Code auf den ersten Blick prägt. Sie ist kurz, sie ist elegant — und sie hat eine Ausnahme, die man kennen muss, weil sie sonst zu Verhalten führt, das völlig unerklärlich wirkt. Die Notation besteht aus einem Gleichheitszeichen und einer spitzen Klammer, die zusammen wie ein Pfeil aussehen — daher der Name.
10:52 Der eigentliche Gewinn steht im zweiten Satz: Besteht der Rumpf aus einem einzigen Ausdruck, wird dessen Wert automatisch zurückgegeben, ohne dass Sie das Schlüsselwort hinschreiben. Das nennt man implizite Rückgabe. Bei kurzen Umformungen schrumpft eine Funktion damit auf wenige Zeichen zusammen — und genau deshalb sehen Sie in modernem Code, der mit Listen arbeitet, fast nur noch diese Form.
11:14 Sie ist nah dran an dem, was man ohnehin denkt. Vergleichen Sie die ersten beiden Blöcke — inhaltlich identisch, einmal mit geschweiften Klammern und ausdrücklicher Rückgabe, einmal ohne beides. Merken Sie sich die Regel dahinter: Geschweifte Klammern bedeuten Rumpf, und dann brauchen Sie die Rückgabe. Kein Rumpf, keine Rückgabe nötig.
11:34 Darunter die Kurzformen für keine und einen Parameter. Und die letzte Zeile ist die berüchtigte: Wenn Sie ein Objektliteral implizit zurückgeben wollen, müssen Sie es in runde Klammern setzen. Sonst hält JavaScript die geschweiften Klammern für einen Funktionsrumpf — und der ist dann leer. Ergebnis: undefined, ohne jede Fehlermeldung.
11:55 Ihre Stärke liegt bei kurzen Callbacks und knappen Umformungen — überall dort, wo die Funktion in eine Zeile passt und der Aufruf drumherum die eigentliche Aussage trägt. Der dritte Punkt ist die angekündigte Ausnahme, und wir behandeln sie hier bewusst nur mit einem Satz: Pfeilfunktionen bringen kein eigenes this mit. In Objektmethoden macht das einen Unterschied, der überraschen kann. Merken Sie sich, dass es diesen Unterschied gibt; die Details kommen, wenn Sie sie brauchen.
12:21 Der Schluss daraus ist maßvoll: Die Pfeilfunktion ist kein vollwertiger Ersatz für jede Funktion, sondern eine kurze Form für kurze Fälle. Schreiben Sie zwei Array-Aufrufe mit impliziter Rückgabe und einen, der ein Objektliteral zurückliefert. Der dritte ist der eigentliche Prüfstein — dort brauchen Sie die runden Klammern, und wenn Sie sie vergessen, sehen Sie sofort, was passiert.
12:43 Der Rest ist Fingerübung, aber eine nützliche: Diese Schreibweise werden Sie in den kommenden Modulen ständig verwenden, und sie soll Ihnen flüssig von der Hand gehen. Achten Sie dabei auf die Lesbarkeit — kurz ist gut, solange die Absicht erkennbar bleibt. Das Objektliteral ohne Klammern liefert stillschweigend undefined — der unangenehmste Fehler dieses Kapitels, weil nichts darauf hinweist.
13:06 Das Gegenstück ist die vergessene Rückgabe bei gesetzten geschweiften Klammern; auch hier kommt undefined heraus. Beide Fehler haben dieselbe Ursache: eine Verwechslung von Rumpf und Ausdruck. Der dritte Punkt ist die this-Frage, die ich benannt habe — in Objektmethoden verhält sich eine Pfeilfunktion anders, als man erwartet.
13:24 Und der letzte ist ein Maßhalte-Hinweis: Mehrzeilige Logik in eine implizite Rückgabe zu pressen, führt zu Ausdrücken, die man mit dem Finger nachfahren muss.
Gültigkeitsbereich und Closures
13:34 Zum Abschluss ein Thema mit dem Ruf, schwierig zu sein. Ist es nicht — wenn man es am richtigen Beispiel sieht statt abstrakt erklärt bekommt. Es geht darum, wo Werte sichtbar sind, und darum, was eine Funktion sich merkt. Der Gültigkeitsbereich beantwortet die Frage, wo eine Variable erreichbar ist — eine Art Sichtbarkeitszone. Das ist der leichte Teil.
13:56 Die Closure ist der Teil, der überrascht: Eine Funktion merkt sich Werte aus dem Bereich, in dem sie erzeugt wurde, und behält sie — auch nachdem die äußere Funktion längst beendet ist. Das klingt zunächst nach einem Widerspruch, denn eigentlich sollte mit dem Ende einer Funktion alles Lokale verschwinden. Tut es auch, außer es merkt sich noch jemand etwas davon.
14:17 Diese Fähigkeit ist kein Kuriosum, sondern eines der nützlichsten Werkzeuge der Sprache. Vier Ebenen, wie ineinandergesteckte Kisten. Ganz außen der globale Bereich, den Sie sparsam benutzen sollten — dazu gleich mehr. Dann der Modulbereich, auf den wir in einem späteren Modul zurückkommen. Innerhalb einer Funktion gilt der Funktionsbereich, und innerhalb geschweifter Klammern der Blockbereich.
14:41 Der Merksatz steht im Fußtext und ist das eigentlich Wichtige: Innere Bereiche sehen die äußeren, umgekehrt gilt das nicht. Von drinnen können Sie hinausschauen, von draußen nicht hinein. Genau diese Einbahnstraße macht Closures möglich — die innere Funktion sieht die Werte der äußeren. Das ist das Beispiel, das Closures greifbar macht.
15:01 Eine Funktion, die eine Funktion zurückgibt — und die zurückgegebene merkt sich den Grenzwert, mit dem die äußere aufgerufen wurde. Unten entstehen zwei Prüffunktionen aus derselben Vorlage, und jede trägt ihren eigenen Grenzwert mit sich. Dieselbe Eingabe, verschiedene Antworten. Sehen Sie das wie eine Kaffeemaschine mit gespeicherter Einstellung: Zwei Maschinen desselben Modells, unterschiedlich eingestellt.
15:25 Der eigentliche Trick ist, dass die äußere Funktion längst beendet ist — und ihr Wert trotzdem noch lebt, weil sich jemand daran erinnert. Bauen Sie zwei Prüffunktionen aus derselben Vorlage, die für dieselbe Eingabe unterschiedliche, jeweils korrekte Ergebnisse liefern. Wenn das funktioniert, haben Sie eine Closure gebaut — und zwar bewusst, was der Unterschied zu dem ist, was viele versehentlich tun.
15:49 Der Hinweis nennt den entscheidenden Punkt: Der gemerkte Wert wird beim Erzeugen übergeben, nicht beim Aufruf. Zwei verschiedene Zeitpunkte, zwei verschiedene Rollen. Wer das einmal auseinandergehalten hat, versteht anschließend auch die Muster, die in Bibliotheken darauf aufbauen — und das sind erstaunlich viele. Der erste Punkt ist ein handfestes Argument gegen das alte Variablen-Schlüsselwort: Es kennt keine Blockgrenzen, nur Funktionsgrenzen.
16:15 Eine Variable, die Sie in einer Schleife anlegen, ist danach draußen weiterhin da — mit allen Überraschungen, die daraus folgen. Der zweite Punkt ist die Einbahnstraße aus der Tabelle: Von außen in einen Block hineinzugreifen, geht nicht. Der dritte ist die Erwartung, die Closures zunächst verletzen — nein, der gemerkte Wert verschwindet nicht.
16:35 Und der letzte ist ein Rat fürs Berufsleben: Zu viel im globalen Bereich abzulegen, erzeugt Namenskollisionen, die man in großen Projekten sehr ungern sucht.
Logik hat jetzt Namen
16:45 Drei Sätze zum Mitnehmen. Erstens die Begriffsklärung, mit der wir angefangen haben: Parameter stehen in der Definition, Argumente kommen beim Aufruf. Zweitens der Unterschied der Schreibweisen — Deklarationen sind gehoistet, Ausdrücke folgen den Regeln von const und let. Und drittens der Satz, der dieses Modul beschließt: Eine Closure merkt sich die Umgebung ihrer Entstehung. Damit hat Ihre Logik Namen bekommen und ist wiederverwendbar geworden.
17:12 Im nächsten Modul bauen wir darauf auf — es geht um funktionale Muster, um Rekursion und um verzögerte Auswertung.
Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →