Wie funktionieren JavaScript-Closures?

Lesezeit: 8 Minuten

Wie würden Sie JavaScript-Closures jemandem erklären, der die Konzepte kennt, aus denen sie bestehen (z. B. Funktionen, Variablen und dergleichen), aber Closures selbst nicht versteht?

ich habe gesehen das Schema-Beispiel auf Wikipedia gegeben, aber leider hat es nicht geholfen.

Jede Funktion in JavaScript behält einen Link zu ihrer äußeren lexikalischen Umgebung bei. Eine lexikalische Umgebung ist eine Abbildung aller Namen (z. B. Variablen, Parameter) innerhalb eines Gültigkeitsbereichs mit ihren Werten.

Also, wann immer Sie die sehen function Schlüsselwort hat Code innerhalb dieser Funktion Zugriff auf Variablen, die außerhalb der Funktion deklariert wurden.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Dies wird protokolliert 16 weil Funktion bar schließt über dem Parameter x und die Variable tmpdie beide in der lexikalischen Umgebung der äußeren Funktion existieren foo.

Funktion barzusammen mit seiner Verbindung mit der lexikalischen Umgebung der Funktion foo ist ein Verschluss.

Eine Funktion muss nicht Rückkehr um einen Verschluss herzustellen. Einfach aufgrund ihrer Deklaration schließt jede Funktion über ihre einschließende lexikalische Umgebung und bildet einen Abschluss.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2);
bar(10); // 16
bar(10); // 17

Die obige Funktion loggt auch 16, weil der Code drin ist bar kann immer noch auf argument verweisen x und variabel tmpobwohl sie nicht mehr direkt in den Geltungsbereich fallen.

Allerdings seit tmp hängt immer noch drinnen herum bar‘s-Abschluss, kann es inkrementiert werden. Sie wird bei jedem Aufruf erhöht bar.

Das einfachste Beispiel für eine Schließung ist dies:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Wenn eine JavaScript-Funktion aufgerufen wird, ein neuer Ausführungskontext ec geschaffen. Zusammen mit den Funktionsargumenten und dem Zielobjekt erhält dieser Ausführungskontext auch einen Link auf die lexikalische Umgebung des aufrufenden Ausführungskontexts, also die in der äußeren lexikalischen Umgebung deklarierten Variablen (im obigen Beispiel beides a und b) sind erhältlich bei ec.

Jede Funktion erzeugt einen Abschluss, da jede Funktion eine Verbindung zu ihrer äußeren lexikalischen Umgebung hat.

Beachten Sie, dass Variablen sich innerhalb eines Verschlusses sichtbar sind, nicht Kopien.

  • Ich liebe diese Erklärung, wirklich. Für diejenigen, die es lesen und nicht folgen, ist die Analogie folgende: Die Funktion princess() ist ein komplexer Bereich, der private Daten enthält. Außerhalb der Funktion können die privaten Daten nicht eingesehen oder abgerufen werden. Die Prinzessin behält die Einhörner, Drachen, Abenteuer etc. in ihrer Fantasie (private Daten) und die Erwachsenen können sie nicht selbst sehen. ABER die Fantasie der Prinzessin wird in dem Verschluss für die eingefangen story() Funktion, die die einzige Schnittstelle ist littleGirl Instanz entführt in die Welt der Magie.

    –Patrick M

    28. Februar 2013 um 7:49 Uhr


  • Undefinierte Werte machen es schwieriger zu verstehen. Hier ist die wahre Geschichte jsfiddle.net/rjdx34k0/3

    – Hugolpz

    2. September 2020 um 19:13 Uhr

  • Oh schön, ich war so nah dran, eine Bearbeitung vorzunehmen, um das zu entfernen, was ich am Anfang für den zusätzlichen Platz hielt. Gute Arbeit, +1

    – Tiago Martins Peres

    23. Oktober 2020 um 7:34 Uhr

  • Und Prince Charming kann zu ihren Abenteuern beitragen, kann alle Drachen töten, um sie vor Gefahren wie unten zu retten: function princeCharming { adventures.push('Honeymoon Trip', 'Skydiving', 'Visiting Somalia'); const pickADragonToKill = dragons.pop(); }

    – Schiwam

    13. Januar 2021 um 5:15 Uhr


Wenn wir die Frage ernst nehmen, sollten wir herausfinden, wozu ein typischer 6-Jähriger kognitiv fähig ist, obwohl zugegebenermaßen jemand, der sich für JavaScript interessiert, nicht so typisch ist.

An Kindheitsentwicklung: 5 bis 7 Jahre es sagt:

Ihr Kind wird in der Lage sein, zweistufigen Anweisungen zu folgen. Wenn Sie zum Beispiel zu Ihrem Kind sagen: „Geh in die Küche und hol mir einen Müllsack“, wird es sich diese Richtung merken können.

Wir können dieses Beispiel verwenden, um Closures wie folgt zu erklären:

Die Küche ist ein Abschluss, der eine lokale Variable namens hat trashBags. Es gibt eine Funktion in der Küche namens getTrashBag das bekommt einen Müllsack und bringt ihn zurück.

Wir können dies in JavaScript wie folgt codieren:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

Weitere Punkte, die erklären, warum Schließungen interessant sind:

  • Jedes Mal makeKitchen() aufgerufen wird, wird ein neuer Abschluss mit einem eigenen separaten erstellt trashBags.
  • Die trashBags Variable ist lokal im Inneren jeder Küche und ist nicht von außen zugänglich, sondern die innere Funktion über die getTrashBag Eigentum hat Zugriff darauf.
  • Jeder Funktionsaufruf erstellt eine Closure, aber es wäre nicht erforderlich, die Closure herumzuhalten, es sei denn, eine innere Funktion, die Zugriff auf das Innere der Closure hat, kann von außerhalb der Closure aufgerufen werden. Zurückgeben des Objekts mit der getTrashBag Funktion macht das hier.

Der Strohmann

Ich muss wissen, wie oft auf eine Schaltfläche geklickt wurde, und bei jedem dritten Klick etwas tun …

Ziemlich offensichtliche Lösung

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Jetzt funktioniert das, aber es greift in den äußeren Bereich ein, indem es eine Variable hinzufügt, deren einziger Zweck darin besteht, die Zählung zu verfolgen. In einigen Situationen wäre dies vorzuziehen, da Ihre äußere Anwendung möglicherweise Zugriff auf diese Informationen benötigt. Aber in diesem Fall ändern wir nur das Verhalten jedes dritten Klicks, also ist es vorzuziehen Schließen Sie diese Funktionalität in den Ereignishandler ein.

Ziehen Sie diese Option in Betracht

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Beachte hier ein paar Dinge.

Im obigen Beispiel verwende ich das Schließverhalten von JavaScript. Dieses Verhalten ermöglicht es jeder Funktion, auf unbestimmte Zeit auf den Bereich zuzugreifen, in dem sie erstellt wurde. Um dies praktisch anzuwenden, rufe ich sofort eine Funktion auf, die eine andere Funktion zurückgibt, und da die Funktion, die ich zurückgebe, Zugriff auf die interne Zählvariable hat (aufgrund des oben erläuterten Schließverhaltens), führt dies zu einem privaten Bereich für die Verwendung durch das Ergebnis Funktion… Nicht so einfach? Verdünnen wir es…

Ein einfacher einzeiliger Verschluss

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a="val"; return function() { alert(a); }; })();

Alle Variablen außerhalb der zurückgegebenen Funktion sind für die zurückgegebene Funktion verfügbar, aber sie sind nicht direkt für das zurückgegebene Funktionsobjekt verfügbar …

func();  // Alerts "val"
func.a;  // Undefined

Kapiert? In unserem Hauptbeispiel ist die count-Variable also in der Closure enthalten und steht dem Event-Handler immer zur Verfügung, sodass sie ihren Status von Klick zu Klick beibehält.

Auch diese private Variable ist state völlig zugänglich, sowohl zum Lesen als auch zum Zuweisen zu seinen privaten Bereichsvariablen.

Da gehst du; Sie kapseln dieses Verhalten jetzt vollständig ein.

Vollständiger Blogbeitrag (einschließlich jQuery-Überlegungen)

Closures sind schwer zu erklären, weil sie verwendet werden, um ein Verhalten zum Funktionieren zu bringen, von dem jeder intuitiv erwartet, dass es funktioniert. Ich finde den besten Weg, sie zu erklären (und wie das geht ich gelernt, was sie tun) ist, sich die Situation ohne sie vorzustellen:

const makePlus = function(x) {
    return function(y) { return x + y; };
}

const plus5 = makePlus(5);
console.log(plus5(3));

Was würde hier passieren, wenn JavaScript nicht Kennen Sie Verschlüsse? Ersetzen Sie einfach den Aufruf in der letzten Zeile durch seinen Methodenkörper (was im Grunde das ist, was Funktionsaufrufe tun) und Sie erhalten:

console.log(x + 3);

Nun, wo ist die Definition von x? Wir haben es nicht im aktuellen Geltungsbereich definiert. Die einzige Lösung ist zu lassen plus5 tragen seinen Gültigkeitsbereich (oder vielmehr den Gültigkeitsbereich seines übergeordneten Elements) herum. Diesen Weg, x ist wohldefiniert und an den Wert 5 gebunden.

  • “Sie werden verwendet, um ein Verhalten zum Laufen zu bringen, von dem jeder intuitiv erwartet, dass es funktioniert.” Schätzen Sie diesen Kommentar, da ich teilweise damit zu kämpfen hatte. Ich hatte das Gefühl, dass mir etwas fehlt, aber es stellte sich heraus, dass ich es nicht war!

    – Shane

    10. Juli 2020 um 18:45 Uhr

  • Die Schließung rettet nur die äußere lexikalische Umgebung. Wenn eine Funktion in einer lexikalischen Umgebung erstellt wurde, bedeutet dies, dass sie Teil des Gedächtnisses dieser lexikalischen Umgebung ist. Wenn ich die Funktion aufrufe, wird ein neuer Ausführungskontext erstellt und eine neue lexikalische Umgebung wird erstellt, und ihre äußere Referenz wird auf die lexikalische Umgebung verweisen, in der die Funktion erstellt wurde.

    – Nadav Shlush

    23. September 2021 um 10:39 Uhr

  • @NadavShlush Das sagt meine Antwort bereits in weniger Worten, ja.

    – Konrad Rudolf

    23. September 2021 um 10:42 Uhr

987130cookie-checkWie funktionieren JavaScript-Closures?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy