JavaScript ist eine sehr funktionsorientierte Sprache. Es gibt uns viel Freiheit. Eine Funktion kann jederzeit erstellt, als Argument an eine andere Funktion übergeben und später von einer völlig anderen Codestelle aus aufgerufen werden.
Wir wissen bereits, dass eine Funktion auf Variablen außerhalb von ihr („äußere“ Variablen) zugreifen kann.
Aber was passiert, wenn sich äußere Variablen seit der Erstellung einer Funktion ändern? Bekommt die Funktion neuere Werte oder die alten?
Und wenn eine Funktion als Argument übergeben und von einer anderen Stelle im Code aufgerufen wird, erhält sie dann Zugriff auf äußere Variablen an der neuen Stelle?
Erweitern wir unser Wissen, um diese und komplexere Szenarien zu verstehen.
Wir werden hier über let/const
-Variablen sprechen
In JavaScript gibt es drei Möglichkeiten, eine Variable zu deklarieren: let
, const
(die modernen) und var
(das Überbleibsel der Vergangenheit).
In diesem Artikel verwenden wir let
-Variablen in Beispielen.
Mit const
deklarierte Variablen verhalten sich gleich, daher geht es in diesem Artikel auch um const
.
Die alte var
weist einige bemerkenswerte Unterschiede auf, diese werden im Artikel Die alte „Var“ behandelt.
Wenn eine Variable innerhalb eines Codeblocks {...}
deklariert wird, ist sie nur innerhalb dieses Blocks sichtbar.
Zum Beispiel:
{ // Erledige einen Job mit lokalen Variablen, die außerhalb nicht sichtbar sein sollten let message = "Hallo"; // nur in diesem Block sichtbar Warnung (Nachricht); // Hallo } Warnung (Nachricht); // Fehler: Nachricht ist nicht definiert
Wir können dies verwenden, um einen Codeabschnitt, der seine eigene Aufgabe ausführt, mit Variablen zu isolieren, die nur dazu gehören:
{ // Nachricht anzeigen let message = "Hallo"; Warnung (Nachricht); } { // eine weitere Nachricht anzeigen let message = „Auf Wiedersehen“; Warnung (Nachricht); }
Ohne Blöcke würde es einen Fehler geben
Bitte beachten Sie, dass es ohne separate Blöcke zu einem Fehler kommen würde, wenn wir let
mit dem vorhandenen Variablennamen verwenden:
// Nachricht anzeigen let message = "Hallo"; Warnung (Nachricht); // eine weitere Nachricht anzeigen let message = „Auf Wiedersehen“; // Fehler: Variable bereits deklariert Warnung (Nachricht);
Denn if
, for
, while
usw. sind in {...}
deklarierte Variablen ebenfalls nur innerhalb sichtbar:
if (wahr) { let Phrase = „Hallo!“; Warnung(Satz); // Hallo! } Warnung(Satz); // Fehler, keine solche Variable!
Nach Abschluss if
Vorgangs wird in der folgenden alert
die phrase
nicht angezeigt, daher der Fehler.
Das ist großartig, da wir damit blocklokale Variablen erstellen können, die für einen if
Zweig spezifisch sind.
Ähnliches gilt für for
und while
-Schleifen:
for (sei i = 0; i < 3; i++) { // Die Variable i ist nur innerhalb dieses for sichtbar alarm(i); // 0, dann 1, dann 2 } alarm(i); // Fehler, keine solche Variable
Visuell sei let i
außerhalb von {...}
. Aber das for
Konstrukt ist hier etwas Besonderes: Die darin deklarierte Variable wird als Teil des Blocks betrachtet.
Eine Funktion wird als „verschachtelt“ bezeichnet, wenn sie innerhalb einer anderen Funktion erstellt wird.
Dies ist mit JavaScript problemlos möglich.
Wir können es verwenden, um unseren Code wie folgt zu organisieren:
Funktion sayHiBye(firstName, lastName) { // verschachtelte Hilfsfunktion zur Verwendung unten Funktion getFullName() { return Vorname + " " + Nachname; } alarm( "Hallo, " + getFullName() ); alarm( "Bye, " + getFullName() ); }
Hier wurde der Einfachheit halber die verschachtelte Funktion getFullName()
erstellt. Es kann auf die äußeren Variablen zugreifen und so den vollständigen Namen zurückgeben. Verschachtelte Funktionen sind in JavaScript weit verbreitet.
Noch interessanter ist, dass eine verschachtelte Funktion zurückgegeben werden kann: entweder als Eigenschaft eines neuen Objekts oder als eigenständiges Ergebnis. Es kann dann woanders verwendet werden. Egal wo, es hat immer noch Zugriff auf dieselben äußeren Variablen.
Unten erstellt makeCounter
die Funktion „counter“, die bei jedem Aufruf die nächste Zahl zurückgibt:
Funktion makeCounter() { lass zählen = 0; Rückgabefunktion() { return count++; }; } let counter = makeCounter(); alarm( counter() ); // 0 alarm( counter() ); // 1 alarm( counter() ); // 2
Obwohl sie einfach sind, haben leicht modifizierte Varianten dieses Codes praktische Verwendungsmöglichkeiten, beispielsweise als Zufallszahlengenerator, um Zufallswerte für automatisierte Tests zu generieren.
Wie funktioniert das? Wenn wir mehrere Zähler erstellen, sind diese dann unabhängig? Was ist hier mit den Variablen los?
Solche Dinge zu verstehen ist großartig für die allgemeine Kenntnis von JavaScript und von Vorteil für komplexere Szenarien. Gehen wir also etwas näher darauf ein.
Hier sind Drachen!
Die ausführliche technische Erklärung liegt vor Ihnen.
Soweit ich niedrigstufige Sprachdetails vermeiden möchte, wäre jedes Verständnis ohne sie mangelhaft und unvollständig, also machen Sie sich bereit.
Der Übersichtlichkeit halber ist die Erklärung in mehrere Schritte unterteilt.
In JavaScript verfügen jede laufende Funktion, jeder Codeblock {...}
und das Skript als Ganzes über ein internes (verstecktes) zugehöriges Objekt, das als Lexical Environment bekannt ist.
Das Lexical Environment-Objekt besteht aus zwei Teilen:
Umgebungsdatensatz – ein Objekt, das alle lokalen Variablen als Eigenschaften speichert (und einige andere Informationen wie den Wert von this
).
Ein Verweis auf die äußere lexikalische Umgebung , die dem äußeren Code zugeordnet ist.
Eine „Variable“ ist lediglich eine Eigenschaft des speziellen internen Objekts Environment Record
. „Eine Variable abrufen oder ändern“ bedeutet „eine Eigenschaft dieses Objekts abrufen oder ändern“.
In diesem einfachen Code ohne Funktionen gibt es nur eine lexikalische Umgebung:
Dies ist die sogenannte globale lexikalische Umgebung, die dem gesamten Skript zugeordnet ist.
Im Bild oben bedeutet das Rechteck Umgebungsdatensatz (Variablenspeicher) und der Pfeil bedeutet die äußere Referenz. Die globale lexikalische Umgebung hat keine äußere Referenz, deshalb zeigt der Pfeil auf null
.
Während die Ausführung des Codes beginnt und fortgesetzt wird, ändert sich die lexikalische Umgebung.
Hier ist ein etwas längerer Code:
Rechtecke auf der rechten Seite zeigen, wie sich die globale lexikalische Umgebung während der Ausführung ändert:
Wenn das Skript startet, ist die Lexikalische Umgebung vorab mit allen deklarierten Variablen gefüllt.
Zunächst befinden sie sich im Zustand „Uninitialized“. Das ist ein spezieller interner Zustand. Das bedeutet, dass die Engine die Variable kennt, sie aber erst referenziert werden kann, wenn sie mit let
deklariert wurde. Es ist fast so, als ob die Variable nicht existieren würde.
Dann erscheint let phrase
Phrasendefinition. Es gibt noch keine Zuweisung, daher ist der Wert undefined
. Ab diesem Punkt können wir die Variable verwenden.
phrase
wird ein Wert zugewiesen.
phrase
ändert den Wert.
Im Moment sieht alles einfach aus, oder?
Eine Variable ist eine Eigenschaft eines speziellen internen Objekts, das dem aktuell ausgeführten Block/der Funktion/dem aktuell ausgeführten Skript zugeordnet ist.
Bei der Arbeit mit Variablen handelt es sich tatsächlich um die Arbeit mit den Eigenschaften dieses Objekts.
Die lexikalische Umgebung ist ein Spezifikationsobjekt
„Lexical Environment“ ist ein Spezifikationsobjekt: Es existiert nur „theoretisch“ in der Sprachspezifikation, um zu beschreiben, wie Dinge funktionieren. Wir können dieses Objekt nicht in unseren Code aufnehmen und direkt bearbeiten.
JavaScript-Engines können es auch optimieren, ungenutzte Variablen verwerfen, um Speicher zu sparen und andere interne Tricks ausführen, solange das sichtbare Verhalten wie beschrieben bleibt.
Eine Funktion ist ebenso ein Wert wie eine Variable.
Der Unterschied besteht darin, dass eine Funktionsdeklaration sofort vollständig initialisiert wird.
Wenn eine lexikalische Umgebung erstellt wird, wird eine Funktionsdeklaration sofort zu einer gebrauchsfertigen Funktion (im Gegensatz zu let
ist diese bis zur Deklaration unbrauchbar).
Aus diesem Grund können wir eine als Funktionsdeklaration deklarierte Funktion bereits vor der eigentlichen Deklaration verwenden.
Hier ist beispielsweise der Anfangszustand der globalen lexikalischen Umgebung, wenn wir eine Funktion hinzufügen:
Dieses Verhalten gilt natürlich nur für Funktionsdeklarationen, nicht für Funktionsausdrücke, bei denen wir einer Variablen eine Funktion zuweisen, z. B. let say = function(name)...
.
Wenn eine Funktion ausgeführt wird, wird zu Beginn des Aufrufs automatisch eine neue lexikalische Umgebung erstellt, um lokale Variablen und Parameter des Aufrufs zu speichern.
Für say("John")
sieht es beispielsweise so aus (die Ausführung befindet sich in der Zeile, die mit einem Pfeil gekennzeichnet ist):
Während des Funktionsaufrufs haben wir zwei lexikalische Umgebungen: die innere (für den Funktionsaufruf) und die äußere (global):
Die innere lexikalische Umgebung entspricht der aktuellen Ausführung von say
. Es hat eine einzige Eigenschaft: name
, das Funktionsargument. Wir haben say("John")
aufgerufen, daher ist der Wert des name
"John"
.
Die äußere lexikalische Umgebung ist die globale lexikalische Umgebung. Es verfügt über die phrase
und die Funktion selbst.
Die innere lexikalische Umgebung hat einen Bezug zur outer
.
Wenn der Code auf eine Variable zugreifen möchte, wird zuerst die innere lexikalische Umgebung durchsucht, dann die äußere, dann die äußerere und so weiter bis zur globalen.
Wenn eine Variable nirgendwo gefunden wird, handelt es sich um einen Fehler im strikten Modus (ohne use strict
wird durch eine Zuweisung an eine nicht vorhandene Variable eine neue globale Variable erstellt, um die Kompatibilität mit altem Code zu gewährleisten).
In diesem Beispiel läuft die Suche wie folgt ab:
Für die name
findet die alert
in say
sie sofort in der inneren lexikalischen Umgebung.
Wenn auf phrase
zugegriffen werden soll, gibt es lokal keine phrase
, daher folgt es dem Verweis auf die äußere lexikalische Umgebung und findet sie dort.
Kehren wir zum makeCounter
-Beispiel zurück.
Funktion makeCounter() { lass zählen = 0; Rückgabefunktion() { return count++; }; } let counter = makeCounter();
Zu Beginn jedes makeCounter()
-Aufrufs wird ein neues Lexical Environment-Objekt erstellt, um Variablen für diesen makeCounter
-Lauf zu speichern.
Wir haben also zwei verschachtelte lexikalische Umgebungen, genau wie im obigen Beispiel:
Der Unterschied besteht darin, dass während der Ausführung von makeCounter()
eine winzige verschachtelte Funktion mit nur einer Zeile erstellt wird: return count++
. Wir führen es noch nicht aus, wir erstellen nur.
Alle Funktionen erinnern sich an die lexikalische Umgebung, in der sie erstellt wurden. Technisch gesehen gibt es hier keine Zauberei: Alle Funktionen verfügen über die versteckte Eigenschaft namens [[Environment]]
, die den Verweis auf die Lexikalische Umgebung beibehält, in der die Funktion erstellt wurde:
counter.[[Environment]]
hat also den Verweis auf die lexikalische Umgebung {count: 0}
. Auf diese Weise merkt sich die Funktion, wo sie erstellt wurde, unabhängig davon, wo sie aufgerufen wird. Die [[Environment]]
Referenz wird zum Zeitpunkt der Funktionserstellung einmalig festgelegt.
Später, wenn counter()
aufgerufen wird, wird eine neue lexikalische Umgebung für den Aufruf erstellt und ihre äußere lexikalische Umgebungsreferenz wird von counter.[[Environment]]
übernommen:
Wenn nun der Code in counter()
nach count
-Variablen sucht, durchsucht er zunächst seine eigene lexikalische Umgebung (leer, da dort keine lokalen Variablen vorhanden sind) und dann die lexikalische Umgebung des äußeren makeCounter()
-Aufrufs, wo er sie findet und ändert .
Eine Variable wird in der lexikalischen Umgebung aktualisiert, in der sie sich befindet.
Hier ist der Zustand nach der Ausführung:
Wenn wir counter()
mehrmals aufrufen, wird die count
an derselben Stelle auf 2
, 3
usw. erhöht.
Schließung
Es gibt einen allgemeinen Programmierbegriff „Abschluss“, den Entwickler im Allgemeinen kennen sollten.
Ein Abschluss ist eine Funktion, die sich ihre äußeren Variablen merkt und auf diese zugreifen kann. In manchen Sprachen ist das nicht möglich, oder eine Funktion muss auf eine spezielle Art und Weise geschrieben werden, um dies zu ermöglichen. Aber wie oben erläutert, sind in JavaScript alle Funktionen von Natur aus Abschlüsse (es gibt nur eine Ausnahme, die in der Syntax „Neue Funktion“ behandelt wird).
Das heißt: Sie merken sich mithilfe einer versteckten [[Environment]]
Eigenschaft automatisch, wo sie erstellt wurden, und dann kann ihr Code auf äußere Variablen zugreifen.
Wenn einem Frontend-Entwickler in einem Vorstellungsgespräch die Frage „Was ist ein Abschluss?“ gestellt wird, wäre eine gültige Antwort eine Definition des Abschlusses und eine Erklärung, dass alle Funktionen in JavaScript Abschlüsse sind, und vielleicht noch ein paar Worte zu technischen Details: die [[Environment]]
Eigenschaft und wie lexikalische Umgebungen funktionieren.
Normalerweise wird eine lexikalische Umgebung mit allen Variablen aus dem Speicher entfernt, nachdem der Funktionsaufruf abgeschlossen ist. Das liegt daran, dass es keine Hinweise darauf gibt. Wie jedes JavaScript-Objekt wird es nur im Speicher gehalten, solange es erreichbar ist.
Wenn es jedoch eine verschachtelte Funktion gibt, die nach dem Ende einer Funktion noch erreichbar ist, dann verfügt sie über die Eigenschaft [[Environment]]
, die auf die lexikalische Umgebung verweist.
In diesem Fall ist die Lexikalische Umgebung auch nach Abschluss der Funktion noch erreichbar, bleibt also am Leben.
Zum Beispiel:
Funktion f() { sei Wert = 123; Rückgabefunktion() { Warnung(Wert); } } sei g = f(); // g.[[Environment]] speichert einen Verweis auf die lexikalische Umgebung // des entsprechenden f()-Aufrufs
Bitte beachten Sie, dass bei mehrmaligem Aufruf f()
und dem Speichern der resultierenden Funktionen auch alle entsprechenden Lexical Environment-Objekte im Speicher erhalten bleiben. Im folgenden Code sind alle drei:
Funktion f() { let value = Math.random(); return function() { alarm(value); }; } // 3 Funktionen im Array, jede davon ist mit der lexikalischen Umgebung verknüpft // aus dem entsprechenden f()-Lauf let arr = [f(), f(), f()];
Ein Objekt der lexikalischen Umgebung stirbt, wenn es nicht mehr erreichbar ist (genau wie jedes andere Objekt). Mit anderen Worten: Es existiert nur, solange es mindestens eine verschachtelte Funktion gibt, die darauf verweist.
Im folgenden Code wird nach dem Entfernen der verschachtelten Funktion die umschließende lexikalische Umgebung (und damit der value
) aus dem Speicher gelöscht:
Funktion f() { sei Wert = 123; Rückgabefunktion() { Warnung(Wert); } } sei g = f(); // solange die g-Funktion existiert, bleibt der Wert im Speicher g = null; // ...und jetzt ist der Speicher aufgeräumt
Wie wir gesehen haben, bleiben theoretisch auch alle äußeren Variablen erhalten, solange eine Funktion aktiv ist.
Aber in der Praxis versuchen JavaScript-Engines, dies zu optimieren. Sie analysieren die Variablenverwendung und wenn aus dem Code ersichtlich ist, dass eine äußere Variable nicht verwendet wird, wird sie entfernt.
Ein wichtiger Nebeneffekt in V8 (Chrome, Edge, Opera) besteht darin, dass diese Variable beim Debuggen nicht mehr verfügbar ist.
Versuchen Sie, das folgende Beispiel in Chrome mit geöffneten Entwicklertools auszuführen.
Wenn es pausiert, geben Sie in der Konsole den Befehl alert(value)
ein.
Funktion f() { let value = Math.random(); Funktion g() { Debugger; // in der Konsole: Typ Alert(Wert); Keine solche Variable! } g zurückgeben; } sei g = f(); G();
Wie Sie sehen konnten, gibt es eine solche Variable nicht! Theoretisch sollte es zugänglich sein, aber die Engine hat es optimiert.
Das kann zu lustigen (wenn auch nicht so zeitaufwändigen) Debugging-Problemen führen. Eine davon – wir können eine gleichnamige äußere Variable anstelle der erwarteten sehen:
let value = "Überraschung!"; Funktion f() { let value = „der nächstgelegene Wert“; Funktion g() { Debugger; // in der Konsole: Typ Alert(Wert); Überraschung! } g zurückgeben; } sei g = f(); G();
Diese Funktion von V8 ist gut zu wissen. Wenn Sie mit Chrome/Edge/Opera debuggen, werden Sie früher oder später darauf stoßen.
Das ist kein Fehler im Debugger, sondern eine Besonderheit von V8. Vielleicht wird es irgendwann geändert. Sie können dies jederzeit überprüfen, indem Sie die Beispiele auf dieser Seite ausführen.
Wichtigkeit: 5
Die Funktion sayHi verwendet einen externen Variablennamen. Welchen Wert wird die Funktion verwenden, wenn sie ausgeführt wird?
let name = „John“; Funktion sayHi() { Alert("Hallo, " + Name); } name = „Pete“; sayHi(); // Was wird angezeigt: „John“ oder „Pete“?
Solche Situationen kommen sowohl bei der browser- als auch bei der serverseitigen Entwicklung häufig vor. Die Ausführung einer Funktion kann später als bei ihrer Erstellung geplant werden, beispielsweise nach einer Benutzeraktion oder einer Netzwerkanforderung.
Die Frage ist also: Werden die neuesten Änderungen übernommen?
Die Antwort lautet: Pete .
Eine Funktion erhält die äußeren Variablen so, wie sie jetzt sind, und verwendet die aktuellsten Werte.
Alte Variablenwerte werden nirgendwo gespeichert. Wenn eine Funktion eine Variable benötigt, übernimmt sie den aktuellen Wert aus ihrer eigenen Lexikalischen Umgebung oder der äußeren.
Wichtigkeit: 5
Die folgende Funktion makeWorker
erstellt eine weitere Funktion und gibt sie zurück. Diese neue Funktion kann von woanders aufgerufen werden.
Wird es von seinem Erstellungsort, vom Aufrufort oder von beiden aus Zugriff auf die äußeren Variablen haben?
Funktion makeWorker() { let name = „Pete“; Rückgabefunktion() { Warnung(Name); }; } let name = „John“; // eine Funktion erstellen let work = makeWorker(); // nenne es arbeiten(); // was wird angezeigt?
Welchen Wert wird angezeigt? „Pete“ oder „John“?
Die Antwort lautet: Pete .
Die Funktion work()
im folgenden Code erhält name
vom Ort ihres Ursprungs durch die äußere lexikalische Umgebungsreferenz:
Das Ergebnis ist hier also "Pete"
.
Aber wenn es in makeWorker()
keinen let name
gäbe, würde die Suche nach draußen gehen und die globale Variable verwenden, wie wir aus der obigen Kette sehen können. In diesem Fall wäre das Ergebnis "John"
.
Wichtigkeit: 5
Hier erstellen wir zwei Zähler: counter
und counter2
mit derselben makeCounter
-Funktion.
Sind sie unabhängig? Was wird der zweite Zähler zeigen? 0,1
oder 2,3
oder etwas anderes?
Funktion makeCounter() { lass zählen = 0; Rückgabefunktion() { return count++; }; } let counter = makeCounter(); let counter2 = makeCounter(); alarm( counter() ); // 0 alarm( counter() ); // 1 alarm( counter2() ); // ? alarm( counter2() ); // ?
Die Antwort: 0,1.
Die Funktionen counter
und counter2
werden durch unterschiedliche Aufrufe von makeCounter
erstellt.
Sie haben also unabhängige äußere lexikalische Umgebungen, jede hat ihre eigene count
.
Wichtigkeit: 5
Hier wird mit Hilfe der Konstruktorfunktion ein Zählerobjekt erstellt.
Wird es funktionieren? Was wird es zeigen?
Funktion Counter() { lass zählen = 0; this.up = function() { return ++count; }; this.down = function() { return --count; }; } let counter = new Counter(); alarm( counter.up() ); // ? alarm( counter.up() ); // ? alarm( counter.down() ); // ?
Sicherlich wird es gut funktionieren.
Beide verschachtelten Funktionen werden in derselben äußeren lexikalischen Umgebung erstellt, sodass sie gemeinsam auf dieselbe count
zugreifen:
Funktion Counter() { lass zählen = 0; this.up = function() { return ++count; }; this.down = function() { return --count; }; } let counter = new Counter(); alarm( counter.up() ); // 1 alarm( counter.up() ); // 2 alarm( counter.down() ); // 1
Wichtigkeit: 5
Schauen Sie sich den Code an. Was wird das Ergebnis des Anrufs in der letzten Zeile sein?
let Phrase = „Hallo“; if (wahr) { let user = „John“; Funktion sayHi() { alarm(`${phrase}, ${user}`); } } sayHi();
Das Ergebnis ist ein Fehler .
Die Funktion sayHi
wird innerhalb von if
deklariert und lebt daher nur darin. Draußen gibt es kein sayHi
.
Wichtigkeit: 4
Schreiben Sie die Funktion sum
, die so funktioniert: sum(a)(b) = a+b
.
Ja, genau so, mit doppelten Klammern (kein Tippfehler).
Zum Beispiel:
Summe(1)(2) = 3 Summe(5)(-1) = 4
Damit die zweiten Klammern funktionieren, müssen die ersten eine Funktion zurückgeben.
So was:
Funktion sum(a) { Rückgabefunktion(b) { gib a + b zurück; // nimmt „a“ aus der äußeren lexikalischen Umgebung }; } alarm( sum(1)(2) ); // 3 alarm( sum(5)(-1) ); // 4
Wichtigkeit: 4
Was wird das Ergebnis dieses Codes sein?
sei x = 1; Funktion func() { console.log(x); // ? sei x = 2; } func();
PS: Bei dieser Aufgabe gibt es eine Falle. Die Lösung liegt nicht auf der Hand.
Das Ergebnis ist: Fehler .
Versuchen Sie es auszuführen:
sei x = 1; Funktion func() { console.log(x); // ReferenceError: Auf „x“ kann vor der Initialisierung nicht zugegriffen werden sei x = 2; } func();
In diesem Beispiel können wir den besonderen Unterschied zwischen einer „nicht vorhandenen“ und einer „nicht initialisierten“ Variablen beobachten.
Wie Sie vielleicht im Artikel „Variablenbereich, Abschluss“ gelesen haben, beginnt eine Variable im „nicht initialisierten“ Zustand ab dem Moment, in dem die Ausführung in einen Codeblock (oder eine Funktion) eintritt. Und es bleibt bis zur entsprechenden let
-Anweisung nicht initialisiert.
Mit anderen Worten: Eine Variable existiert technisch gesehen, kann aber nicht vor let
verwendet werden.
Der obige Code demonstriert es.
Funktion func() { // die lokale Variable x ist der Engine vom Beginn der Funktion an bekannt, // aber „uninitialisiert“ (unbrauchbar) bis let („tote Zone“) // daher der Fehler console.log(x); // ReferenceError: Auf „x“ kann vor der Initialisierung nicht zugegriffen werden sei x = 2; }
Diese Zone der vorübergehenden Unbrauchbarkeit einer Variablen (vom Anfang des Codeblocks bis let
) wird manchmal als „tote Zone“ bezeichnet.
Wichtigkeit: 5
Wir haben eine integrierte Methode arr.filter(f)
für Arrays. Es filtert alle Elemente durch die Funktion f
. Wenn true
zurückgegeben wird, wird dieses Element im resultierenden Array zurückgegeben.
Erstellen Sie eine Reihe „gebrauchsfertiger“ Filter:
inBetween(a, b)
– zwischen a
und b
oder gleich ihnen (einschließlich).
inArray([...])
– im angegebenen Array.
Die Verwendung muss wie folgt aussehen:
arr.filter(inBetween(3,6))
– wählt nur Werte zwischen 3 und 6 aus.
arr.filter(inArray([1,2,3]))
– wählt nur Elemente aus, die mit einem der Mitglieder von [1,2,3]
übereinstimmen.
Zum Beispiel:
/* .. Ihr Code für inBetween und inArray */ sei arr = [1, 2, 3, 4, 5, 6, 7]; alarm( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 alarm( arr.filter(inArray([1, 2, 10])) ); // 1,2
Öffnen Sie eine Sandbox mit Tests.
Funktion inBetween(a, b) { Rückgabefunktion(x) { return x >= a && x <= b; }; } sei arr = [1, 2, 3, 4, 5, 6, 7]; alarm( arr.filter(inBetween(3, 6)) ); // 3,4,5,6
Funktion inArray(arr) { Rückgabefunktion(x) { return arr.includes(x); }; } sei arr = [1, 2, 3, 4, 5, 6, 7]; alarm( arr.filter(inArray([1, 2, 10])) ); // 1,2
Öffnen Sie die Lösung mit Tests in einer Sandbox.
Wichtigkeit: 5
Wir müssen ein Array von Objekten sortieren:
letuser = [ { Name: „John“, Alter: 20, Nachname: „Johnson“ }, { Name: „Pete“, Alter: 18, Nachname: „Peterson“ }, { Name: „Ann“, Alter: 19, Nachname: „Hathaway“ } ];
Der übliche Weg, dies zu tun, wäre:
// mit Namen (Ann, John, Pete) user.sort((a, b) => a.name > b.name ? 1 : -1); // nach Alter (Pete, Ann, John) user.sort((a, b) => a.age > b.age ? 1 : -1);
Können wir es so noch weniger ausführlich gestalten?
users.sort(byField('name')); users.sort(byField('age'));
Anstatt also eine Funktion zu schreiben, geben Sie einfach byField(fieldName)
ein.
Schreiben Sie die Funktion byField
, die dafür verwendet werden kann.
Öffnen Sie eine Sandbox mit Tests.
Funktion byField(fieldName){ return (a, b) => a[fieldName] > b[fieldName] ? 1 : -1; }
Öffnen Sie die Lösung mit Tests in einer Sandbox.
Wichtigkeit: 5
Der folgende Code erstellt ein Array von shooters
.
Jede Funktion soll ihre Nummer ausgeben. Aber etwas stimmt nicht ...
Funktion makeArmy() { let Shooters = []; sei i = 0; while (i < 10) { let Shooter = function() { // eine Shooter-Funktion erstellen, alarm( i ); // das sollte seine Nummer anzeigen }; Shooters.push(Shooter); // und zum Array hinzufügen i++; } // ...und die Reihe der Schützen zurückgeben Return-Schützen; } let army = makeArmy(); // alle Schützen zeigen 10 statt ihrer Nummern 0, 1, 2, 3... army[0](); // 10 vom Schützen Nummer 0 Armee[1](); // 10 vom Schützen Nummer 1 Armee[2](); // 10 ...und so weiter.
Warum zeigen alle Schützen den gleichen Wert?
Korrigieren Sie den Code, damit er wie vorgesehen funktioniert.
Öffnen Sie eine Sandbox mit Tests.
Lassen Sie uns untersuchen, was genau in makeArmy
passiert, und die Lösung wird offensichtlich.
Es erstellt ein leeres Array- shooters
:
let Shooters = [];
Füllt es mit Funktionen über shooters.push(function)
in der Schleife.
Jedes Element ist eine Funktion, daher sieht das resultierende Array wie folgt aus:
Schützen = [ function () { alarm(i); }, function () { alarm(i); }, function () { alarm(i); }, function () { alarm(i); }, function () { alarm(i); }, function () { alarm(i); }, function () { alarm(i); }, function () { alarm(i); }, function () { alarm(i); }, function () { alarm(i); } ];
Das Array wird von der Funktion zurückgegeben.
Dann, später, ruft der Aufruf eines beliebigen Mitglieds, z. B. army[5]()
, das Element army[5]
aus dem Array (das eine Funktion ist) ab und ruft es auf.
Warum zeigen nun alle diese Funktionen den gleichen Wert an, 10
?
Das liegt daran, dass es in shooter
-Funktionen keine lokale Variable i
gibt. Wenn eine solche Funktion aufgerufen wird, übernimmt sie i
aus ihrer äußeren lexikalischen Umgebung.
Welchen Wert wird dann i
haben?
Wenn wir uns die Quelle ansehen:
Funktion makeArmy() { ... sei i = 0; while (i < 10) { let Shooter = function() { // Shooter-Funktion alarm( i ); // sollte seine Nummer anzeigen }; Shooters.push(Shooter); // Funktion zum Array hinzufügen i++; } ... }
Wir können sehen, dass alle shooter
-Funktionen in der lexikalischen Umgebung der Funktion makeArmy()
erstellt werden. Aber wenn army[5]()
aufgerufen wird, hat makeArmy
seine Arbeit bereits beendet und der Endwert von i
ist 10
( while
er bei i=10
stoppt).
Als Ergebnis erhalten alle shooter
-Funktionen denselben Wert aus der äußeren lexikalischen Umgebung, nämlich den letzten Wert, i=10
.
Wie Sie oben sehen können, wird bei jeder Iteration eines while {...}
Blocks eine neue lexikalische Umgebung erstellt. Um dies zu beheben, können wir den Wert von i
wie folgt in eine Variable innerhalb des while {...}
Blocks kopieren:
Funktion makeArmy() { let Shooters = []; sei i = 0; while (i < 10) { sei j = i; let Shooter = function() { // Shooter-Funktion alarm( j ); // sollte seine Nummer anzeigen }; Shooters.push(Shooter); i++; } Return-Schützen; } let army = makeArmy(); // Jetzt funktioniert der Code korrekt army[0](); // 0 army[5](); // 5
Hier deklariert let j = i
eine „iterationslokale“ Variable j
und kopiert i
hinein. Primitive werden „nach Wert“ kopiert, sodass wir tatsächlich eine unabhängige Kopie von i
erhalten, die zur aktuellen Schleifeniteration gehört.
Die Shooter funktionieren korrekt, da der Wert von i
jetzt etwas näher liegt. Nicht in der lexikalischen Umgebung makeArmy()
, sondern in der lexikalischen Umgebung, die der aktuellen Schleifeniteration entspricht:
Ein solches Problem könnte auch vermieden werden, wenn wir am Anfang for
verwenden würden, etwa so:
Funktion makeArmy() { let Shooters = []; for(let i = 0; i < 10; i++) { let Shooter = function() { // Shooter-Funktion alarm( i ); // sollte seine Nummer anzeigen }; Shooters.push(Shooter); } Return-Schützen; } let army = makeArmy(); army[0](); // 0 army[5](); // 5
Das ist im Wesentlichen dasselbe, denn for
wird bei jeder Iteration eine neue lexikalische Umgebung mit einer eigenen Variablen i
generiert. Der in jeder Iteration generierte shooter
verweist also auf sein eigenes i
aus dieser Iteration.
Da Sie sich so viel Mühe gegeben haben, dies zu lesen, und das endgültige Rezept so einfach ist – verwenden Sie es einfach for
, fragen Sie sich vielleicht: Hat sich das gelohnt?
Wenn Sie die Frage einfach beantworten könnten, würden Sie die Lösung nicht lesen. Hoffentlich hat Ihnen diese Aufgabe geholfen, die Dinge etwas besser zu verstehen.
Außerdem gibt es tatsächlich Fälle, in denen man while
for
, und andere Szenarien, in denen solche Probleme real sind.
Öffnen Sie die Lösung mit Tests in einer Sandbox.