Closures – viele von Ihnen JavaScript Devs haben diesen Begriff wahrscheinlich schon einmal gehört. Als ich meine Reise mit JavaScript begann, begegnete ich Closures häufig. Und ich denke, sie sind eines der wichtigsten und interessantesten Konzepte in JavaScript.
Sie halten sie für uninteressant? Das passiert oft, wenn man ein Konzept nicht versteht – man findet es nicht interessant. (Ich weiß nicht, ob Ihnen das auch passiert, aber bei mir ist das der Fall).
Also werde ich in diesem Artikel versuchen, Closures für Sie interessant zu machen.
Bevor wir in die Welt der Closures einsteigen, lassen Sie uns zuerst lexikalisches Scoping verstehen. Wenn Sie bereits darüber Bescheid wissen, überspringen Sie den nächsten Teil. Andernfalls springen Sie hinein, um Closures besser zu verstehen.
Lexical Scoping
Sie denken vielleicht – ich kenne den lokalen und globalen Bereich, aber was zum Teufel ist der lexikalische Bereich? Ich habe genauso reagiert, als ich diesen Begriff hörte. Kein Grund zur Sorge! Schauen wir uns das mal genauer an.
Es ist einfach wie die anderen beiden Scopes:
function greetCustomer() { var customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); // Hi! anchal } greetingMsg();}
Sie können aus der obigen Ausgabe sehen, dass die innere Funktion auf die Variable der äußeren Funktion zugreifen kann. Das ist lexikalisches Scoping, bei dem der Gültigkeitsbereich und der Wert einer Variablen dadurch bestimmt wird, wo sie definiert/erzeugt wird (d.h. ihre Position im Code). Haben Sie es verstanden?
Ich weiß, dass dieser letzte Teil Sie vielleicht verwirrt hat. Lassen Sie mich also tiefer einsteigen. Wussten Sie, dass lexikalisches Scoping auch als statisches Scoping bekannt ist? Ja, das ist der andere Name.
Es gibt auch dynamisches Scoping, das einige Programmiersprachen unterstützen. Warum habe ich dynamisches Scoping erwähnt? Weil es Ihnen helfen kann, lexikalisches Scoping besser zu verstehen.
Schauen wir uns einige Beispiele an:
function greetingMsg() { console.log(customerName);// ReferenceError: customerName is not defined}function greetCustomer() { var customerName = "anchal"; greetingMsg();}greetCustomer();
Sind Sie mit der Ausgabe einverstanden? Ja, es wird ein Referenzfehler ausgegeben. Das liegt daran, dass beide Funktionen keinen Zugriff auf den Geltungsbereich des jeweils anderen haben, da sie separat definiert sind.
Schauen wir uns ein weiteres Beispiel an:
function addNumbers(number1) { console.log(number1 + number2);}function addNumbersGenerate() { var number2 = 10; addNumbers(number2);}addNumbersGenerate();
Die obige Ausgabe ist 20 für eine dynamisch skopierte Sprache. Sprachen, die lexikalisches Scoping unterstützen, geben referenceError: number2 is not defined
. Warum?
Weil bei dynamischem Scoping zuerst in der lokalen Funktion gesucht wird, dann in der Funktion, die diese lokale Funktion aufgerufen hat. Dann wird in der Funktion gesucht, die diese Funktion aufgerufen hat, und so weiter, den Aufrufstapel hinauf.
Der Name ist selbsterklärend – „dynamisch“ bedeutet Veränderung. Der Geltungsbereich und der Wert einer Variablen können unterschiedlich sein, da es davon abhängt, von wo aus die Funktion aufgerufen wird. Die Bedeutung einer Variablen kann sich zur Laufzeit ändern.
Haben Sie das Wesentliche von dynamischem Scoping verstanden? Wenn ja, dann erinnern Sie sich einfach daran, dass lexikalisches Scoping sein Gegenteil ist.
Beim lexikalischen Scoping wird zuerst in der lokalen Funktion gesucht, dann in der Funktion, innerhalb der diese Funktion definiert ist. Dann wird in der Funktion gesucht, innerhalb derer diese Funktion definiert ist und so weiter.
Das lexikalische oder statische Scoping bedeutet also, dass der Gültigkeitsbereich und der Wert einer Variablen von dort aus bestimmt wird, wo sie definiert ist. Er ändert sich nicht.
Schauen wir uns noch einmal das obige Beispiel an und versuchen Sie, die Ausgabe selbst herauszufinden. Nur eine Wendung – deklarieren Sie number2
oben:
var number2 = 2;function addNumbers(number1) { console.log(number1 + number2);}function addNumbersGenerate() { var number2 = 10; addNumbers(number2);}addNumbersGenerate();
Wissen Sie, was die Ausgabe sein wird?
Korrekt – es ist 12 für lexikalisch skopierte Sprachen. Das liegt daran, dass es zuerst in einer addNumbers
-Funktion nachschaut (innerster Scope) und dann nach innen sucht, wo diese Funktion definiert ist. Da es die number2
-Variable erhält, ist die Ausgabe 12.
Sie fragen sich vielleicht, warum ich hier so viel Zeit auf lexikalisches Scoping verwendet habe. Dies ist ein Artikel über Closures, nicht über lexikalisches Scoping. Aber wenn Sie nicht über lexikalisches Scoping Bescheid wissen, werden Sie Closures nicht verstehen.
Warum? Die Antwort erhalten Sie, wenn wir uns die Definition einer Closure ansehen. Lassen Sie uns also in die Spur kommen und auf Closures zurückkommen.
Was ist eine Closure?
Schauen wir uns die Definition einer Closure an:
Eine Closure entsteht, wenn eine innere Funktion Zugriff auf die Variablen und Argumente der äußeren Funktion hat. Die innere Funktion hat Zugriff auf –
1. Eigene Variablen.
2. Variablen und Argumente der äußeren Funktion.
3. Globale Variablen.
Warte! Ist das die Definition einer Closure oder lexikalisches Scoping? Beide Definitionen sehen gleich aus. Wie unterscheiden sie sich?
Nun, das ist der Grund, warum ich oben lexikalisches Scoping definiert habe. Weil Closures mit lexikalischem/statischem Scoping verwandt sind.
Schauen wir uns noch einmal die andere Definition an, die Ihnen sagen wird, wie sich Closures unterscheiden.
Closure ist, wenn eine Funktion in der Lage ist, auf ihren lexikalischen Bereich zuzugreifen, selbst wenn diese Funktion außerhalb ihres lexikalischen Bereichs ausgeführt wird.
Oder,
Eine Funktion kann auf ihren übergeordneten Bereich zugreifen, auch wenn die übergeordnete Funktion bereits ausgeführt wird.
Verwirrt? Keine Sorge, wenn Sie den Sinn noch nicht verstanden haben. Ich habe Beispiele, die Ihnen helfen, es besser zu verstehen. Lassen Sie uns das erste Beispiel für lexikalisches Scoping modifizieren:
function greetCustomer() { const customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); } return greetingMsg;}const callGreetCustomer = greetCustomer();callGreetCustomer(); // output – Hi! anchal
Der Unterschied in diesem Code ist, dass wir die innere Funktion zurückgeben und später ausführen. In einigen Programmiersprachen existiert die lokale Variable während der Ausführung der Funktion. Aber sobald die Funktion ausgeführt wird, existieren diese lokalen Variablen nicht mehr und es kann nicht mehr auf sie zugegriffen werden.
Hier ist die Situation jedoch anders. Nachdem die übergeordnete Funktion ausgeführt wurde, kann die innere Funktion (die zurückgegebene Funktion) immer noch auf die Variablen der übergeordneten Funktion zugreifen. Ja, Sie haben es richtig erraten. Closures sind der Grund dafür.
Die innere Funktion behält ihren lexikalischen Gültigkeitsbereich bei, wenn die übergeordnete Funktion ausgeführt wird, und daher kann diese innere Funktion später auf diese Variablen zugreifen.
Um ein besseres Gefühl dafür zu bekommen, wollen wir mit der Methode dir()
der Konsole in die Liste der Eigenschaften von callGreetCustomer
schauen:
console.dir(callGreetCustomer);
Aus dem obigen Bild, können Sie sehen, wie die innere Funktion ihren übergeordneten Bereich (customerName
) beibehält, wenn greetCustomer()
ausgeführt wird. Und später verwendet es customerName
, wenn callGreetCustomer()
ausgeführt wird.
Ich hoffe, dieses Beispiel hat Ihnen geholfen, die obige Definition einer Closure besser zu verstehen. Und vielleicht machen Ihnen Closures jetzt ein bisschen mehr Spaß.
Was nun? Machen wir das Thema interessanter, indem wir uns verschiedene Beispiele ansehen.
Beispiele für Closures in Aktion
function counter() { let count = 0; return function() { return count++; };}const countValue = counter();countValue(); // 0countValue(); // 1countValue(); // 2
Bei jedem Aufruf von countValue
wird der Wert der Variablen count um 1 erhöht. Moment – dachten Sie, dass der Wert von count 0 ist?
Tja, das wäre falsch, denn eine Closure arbeitet nicht mit einem Wert. Sie speichert die Referenz der Variablen. Wenn wir also den Wert aktualisieren, spiegelt sich das im zweiten oder dritten Aufruf wieder und so weiter, da die Closure die Referenz speichert.
Fühlen Sie sich jetzt etwas klarer? Schauen wir uns ein weiteres Beispiel an:
function counter() { let count = 0; return function () { return count++; };}const countValue1 = counter();const countValue2 = counter();countValue1(); // 0countValue1(); // 1countValue2(); // 0countValue2(); // 1
Ich hoffe, Sie haben die richtige Antwort erraten. Wenn nicht, hier ist der Grund. Als countValue1
und countValue2
, behalten beide ihren eigenen lexikalischen Bereich. Sie haben unabhängige lexikalische Umgebungen. Sie können dir()
verwenden, um den ]
Wert in beiden Fällen zu überprüfen.
Lassen Sie uns ein drittes Beispiel betrachten.
Dieses ist ein bisschen anders. In ihm müssen wir eine Funktion schreiben, um die Ausgabe zu erreichen:
const addNumberCall = addNumber(7);addNumberCall(8) // 15addNumberCall(6) // 13
Einfach. Nutzen Sie Ihr neu gewonnenes Closure-Wissen:
function addNumber(number1) { return function (number2) { return number1 + number2; };}
Nun schauen wir uns ein paar knifflige Beispiele an:
function countTheNumber() { var arrToStore = ; for (var x = 0; x < 9; x++) { arrToStore = function () { return x; }; } return arrToStore;}const callInnerFunctions = countTheNumber();callInnerFunctions() // 9callInnerFunctions() // 9
Jedes Array-Element, das eine Funktion speichert, gibt eine Ausgabe von 9. Haben Sie richtig geraten? Ich hoffe es, aber lassen Sie mich Ihnen trotzdem den Grund verraten. Das liegt an dem Verhalten der Closure.
Die Closure speichert die Referenz, nicht den Wert. Wenn die Schleife das erste Mal durchläuft, ist der Wert von x 0. Beim zweiten Mal ist x dann 1, und so weiter. Da die Closure die Referenz speichert, ändert sie bei jedem Schleifendurchlauf den Wert von x. Und am Ende wird der Wert von x 9 sein. Also gibt callInnerFunctions()
eine Ausgabe von 9.
Aber was, wenn Sie eine Ausgabe von 0 bis 8 wollen? Ganz einfach! Verwenden Sie eine Closure.
Denken Sie darüber nach, bevor Sie sich die folgende Lösung ansehen:
function callTheNumber() { function getAllNumbers(number) { return function() { return number; }; } var arrToStore = ; for (var x = 0; x < 9; x++) { arrToStore = getAllNumbers(x); } return arrToStore;}const callInnerFunctions = callTheNumber();console.log(callInnerFunctions()); // 0console.log(callInnerFunctions()); // 1
Hier haben wir für jede Iteration einen eigenen Bereich erstellt. Sie können console.dir(arrToStore)
verwenden, um den Wert von x in ]
für verschiedene Array-Elemente zu überprüfen.
Das war’s! Ich hoffe, Sie können jetzt sagen, dass Sie Closures interessant finden.
Um meine anderen Artikel zu lesen, besuchen Sie mein Profil hier.