7619 Stimmen

Wie funktionieren die JavaScript-Schließungen?

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

Ich habe gesehen das Beispiel Schema auf Wikipedia angegeben, aber leider hat es nicht geholfen.

26voto

Abrar Jahin Punkte 13034

Mit Closures können JavaScript-Programmierer besseren Code schreiben. Kreativ, ausdrucksstark und prägnant. Wir verwenden häufig Closures in JavaScript, und unabhängig von unserer JavaScript-Erfahrung werden wir ihnen zweifellos immer wieder begegnen. Closures mögen komplex erscheinen, aber nach der Lektüre dieses Artikels werden sie hoffentlich viel leichter zu verstehen sein und somit für Ihre täglichen JavaScript-Programmieraufgaben attraktiver werden.

Sie sollten vertraut sein mit Umfang von JavaScript-Variablen bevor Sie weiter lesen, denn um Closures zu verstehen, müssen Sie den Variablenbereich von JavaScript verstehen.

Was ist ein Abschluss?

Eine Closure ist eine innere Funktion, die Zugriff auf die Variablen-Umfangskette der äußeren (einschließenden) Funktion hat. Die Closure hat drei Scope-Ketten: Sie hat Zugriff auf ihren eigenen Scope (Variablen, die zwischen ihren geschweiften Klammern definiert sind), sie hat Zugriff auf die Variablen der äußeren Funktion und sie hat Zugriff auf die globalen Variablen.

Die innere Funktion hat nicht nur Zugriff auf die Variablen der äußeren Funktion, sondern auch auf die Parameter der äußeren Funktion. Beachten Sie jedoch, dass die innere Funktion das Argumente-Objekt der äußeren Funktion nicht aufrufen kann, auch wenn sie die Parameter der äußeren Funktion direkt aufrufen kann.

Sie erstellen einen Abschluss, indem Sie eine Funktion innerhalb einer anderen Funktion hinzufügen.

Ein grundlegendes Beispiel für Closures in JavaScript:

function showName (firstName, lastName) {
  var nameIntro = "Your name is ";
  // this inner function has access to the outer function's variables, including the parameter
  function makeFullName () {            
    return nameIntro + firstName + " " + lastName;        
  }

  return makeFullName ();
}

showName ("Michael", "Jackson"); // Your name is Michael Jackson

Closures werden in Node.js ausgiebig verwendet; sie sind die Arbeitspferde der asynchronen, nicht-blockierenden Architektur von Node.js. Closures werden auch häufig in jQuery und so ziemlich jedem Stück JavaScript-Code verwendet, das Sie lesen.

Ein klassisches jQuery-Beispiel für Closures:

$(function() {

  var selections = []; 
  $(".niners").click(function() { // this closure has access to the selections variable
    selections.push (this.prop("name")); // update the selections variable in the outer function's scope
  });
});

Regeln und Nebenwirkungen von Schließungen

1. Closures haben auch nach der Rückkehr der äußeren Funktion Zugriff auf die Variable der äußeren Funktion:

Eine der wichtigsten und heikelsten Eigenschaften von Closures ist, dass die innere Funktion immer noch Zugriff auf die Variablen der äußeren Funktion hat, selbst nachdem die äußere Funktion zurückgekehrt ist. Ja, Sie haben das richtig gelesen. Wenn Funktionen in JavaScript ausgeführt werden, verwenden sie dieselbe Gültigkeitsbereichskette, die beim Erstellen der Funktion in Kraft war. Das bedeutet, dass die innere Funktion auch nach der Rückkehr der äußeren Funktion noch Zugriff auf die Variablen der äußeren Funktion hat. Daher können Sie die innere Funktion später in Ihrem Programm aufrufen. Dieses Beispiel demonstriert dies:

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
    // this inner function has access to the outer function's variables, including the parameter
   function lastName (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}

var mjName = celebrityName ("Michael"); // At this juncture, the celebrityName outer function has returned.

// The closure (lastName) is called here after the outer function has returned above
// Yet, the closure still has access to the outer function's variables and parameter
mjName ("Jackson"); // This celebrity is Michael Jackson

2. Closures speichern Referenzen auf die Variablen der äußeren Funktion:

Sie speichern nicht den tatsächlichen Wert. Closures werden noch interessanter, wenn sich der Wert der Variablen der äußeren Funktion ändert, bevor die Closure aufgerufen wird. Und diese leistungsstarke Funktion kann auf kreative Weise genutzt werden, wie dieses Beispiel für private Variablen, das erstmals von Douglas Crockford gezeigt wurde:

function celebrityID () {
    var celebrityID = 999;
    // We are returning an object with some inner functions
    // All the inner functions have access to the outer function's variables
    return {
        getID: function ()  {
            // This inner function will return the UPDATED celebrityID variable
            // It will return the current value of celebrityID, even after the changeTheID function changes it
          return celebrityID;
        },
        setID: function (theNewID)  {
            // This inner function will change the outer function's variable anytime
            celebrityID = theNewID;
        }
    }

}

var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned.
mjID.getID(); // 999
mjID.setID(567); // Changes the outer function's variable
mjID.getID(); // 567: It returns the updated celebrityId variable

3. Schiefgelaufene Schließungen

Da Closures Zugriff auf die aktualisierten Werte der Variablen der äußeren Funktion haben, können sie auch zu Fehlern führen, wenn sich die Variable der äußeren Funktion mit einer for-Schleife ändert. Daher:

// This example is explained in detail below (just after this code box).
function celebrityIDCreator (theCelebrities) {
    var i;
    var uniqueID = 100;
    for (i = 0; i < theCelebrities.length; i++) {
      theCelebrities[i]["id"] = function ()  {
        return uniqueID + i;
      }
    }

    return theCelebrities;
}

var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];

var createIdForActionCelebs = celebrityIDCreator (actionCelebs);

var stalloneID = createIdForActionCelebs [0];    console.log(stalloneID.id()); // 103


Mehr dazu finden Sie hier.

  1. http://javascript.info/tutorial/closures

  2. http://www.javascriptkit.com/javatutors/closures.shtml

24voto

Rafael Eyng Punkte 4232

(Ich berücksichtige nicht die Sache mit dem 6-Jährigen).

In einer Sprache wie JavaScript, in der Sie Funktionen als Parameter an andere Funktionen übergeben können (Sprachen, in denen Funktionen Bürger erster Klasse ), werden Sie sich oft dabei ertappen, dass Sie etwas tun wie:

var name = 'Rafael';

var sayName = function() {
  console.log(name);
};

Sie sehen, sayName hat nicht die Definition für die name Variable, aber sie verwendet den Wert von name die außerhalb von sayName (in einem übergeordneten Bereich).

Sagen wir, Sie bestehen sayName als Parameter für eine andere Funktion, die dann sayName als Rückruf:

functionThatTakesACallback(sayName);

Beachten Sie das:

  1. sayName wird von innen aufgerufen functionThatTakesACallback (gehen Sie davon aus, dass ich, da ich nicht implementiert habe functionThatTakesACallback in diesem Beispiel).
  2. Wenn sayName aufgerufen wird, protokolliert es den Wert der name variabel.
  3. functionThatTakesACallback definiert nicht eine name variabel ist (nun, es könnte, aber es würde keine Rolle spielen, also nehmen Sie an, dass es nicht so ist).

Wir haben also sayName im Inneren aufgerufen werden functionThatTakesACallback und unter Bezugnahme auf eine name Variable, die nicht innerhalb von functionThatTakesACallback .

Was passiert dann? A ReferenceError: name is not defined ?

Nein! Der Wert von name innerhalb einer Verschluss . Sie können sich diesen Abschluss vorstellen als Kontext, der mit einer Funktion verbunden ist die die Werte enthält, die an dem Ort verfügbar waren, an dem diese Funktion definiert wurde.

Also: Auch wenn name nicht im Geltungsbereich der Funktion sayName aufgerufen werden (innerhalb von functionThatTakesACallback ), sayName kann auf den Wert für name die in der Schließung erfasst wird, die mit sayName .

--

Aus dem Buch Eloquentes JavaScript :

Ein gutes mentales Modell ist es, sich Funktionswerte so vorzustellen, dass sie sowohl den Code in ihrem Körper als auch die Umgebung, in der sie erstellt werden, enthalten. Beim Aufruf sieht der Funktionskörper seine ursprüngliche Umgebung, nicht die Umgebung, in der der Aufruf erfolgt.

24voto

Andy Punkte 6895

Hier ist die Zen-Antwort, die ich geben kann:

Was würden Sie von diesem Code erwarten? Sagen Sie es mir in einem Kommentar, bevor Sie ihn ausführen. Ich bin neugierig!

function foo() {
  var i = 1;
  return function() {
    console.log(i++);
  }
}

var bar = foo();
bar();
bar();
bar();

var baz = foo();
baz();
baz();
baz();

Öffnen Sie nun die Konsole in Ihrem Browser ( Ctrl + Shift + I o F12 (hoffentlich) und fügen Sie den Code ein und drücken Sie Enter .

Wenn dieser Code das ausgibt, was Sie erwarten (JavaScript-Neulinge - ignorieren Sie das "undefined" am Ende), dann haben Sie bereits wortloses Verstehen . In Worten wird die Variable i ist Teil der inneren Funktion Instanz Schließung.

Ich habe es so formuliert, weil ich, nachdem ich verstanden hatte, dass dieser Code Instanzen von foo() die innere Funktion in bar y baz und sie dann über diese Variablen aufzurufen, hat mich nichts weiter überrascht.

Aber wenn ich falsch liege und die Konsolenausgabe Sie überrascht hat, lassen Sie es mich wissen!

22voto

Vitim.us Punkte 18320

Gegeben sei die folgende Funktion

function person(name, age){

    var name = name;
    var age = age;

    function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }

    return introduce;
}

var a = person("Jack",12);
var b = person("Matt",14);

Jedes Mal, wenn die Funktion person aufgerufen wird, wird ein neuer Abschluss erstellt. Während Variablen a y b haben die gleiche introduce Funktion ist sie mit verschiedenen Verschlüssen verbunden. Und dieser Abschluss wird auch dann noch existieren, wenn die Funktion person beendet die Ausführung.

Enter image description here

a(); //My name is Jack, and I'm 12
b(); //My name is Matt, and I'm 14

Eine abstrakte Schließung könnte etwa so dargestellt werden:

closure a = {
    name: "Jack",
    age: 12,
    call: function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }
}

closure b = {
    name: "Matt",
    age: 14,
    call: function introduce(){
        alert("My name is "+name+", and I'm "+age);
    }
}

Angenommen, Sie wissen, wie ein class in einer anderen Sprache arbeiten, werde ich eine Analogie herstellen.

Denken wie

  • JavaScript function als constructor
  • local variables como instance properties
  • diese properties sind privat
  • inner functions como instance methods

Jedes Mal, wenn ein function heißt

  • Eine neue object die alle lokalen Variablen enthält, wird erstellt.
  • Die Methoden dieses Objekts haben Zugriff auf "properties" dieses Instanzobjekts.

22voto

roland Punkte 7345

Je mehr ich über den Abschluss nachdenke, desto mehr sehe ich ihn als einen zweistufigen Prozess: init - Aktion

init: pass first what's needed...
action: in order to achieve something for later execution.

Für einen 6-Jährigen würde ich den Schwerpunkt auf die praktischer Aspekt der Schließung:

Daddy: Listen. Could you bring mum some milk (2).
Tom: No problem.
Daddy: Take a look at the map that Daddy has just made: mum is there and daddy is here.
Daddy: But get ready first. And bring the map with you (1), it may come in handy
Daddy: Then off you go (3). Ok?
Tom: A piece of cake!

Beispiel : Bringen Sie der Mutter etwas Milch (=Aktion). Mach dich zuerst fertig und bring die Karte mit (=init).

function getReady(map) {
    var cleverBoy = 'I examine the ' + map;
    return function(what, who) {
        return 'I bring ' + what + ' to ' + who + 'because + ' cleverBoy; //I can access the map
    }
}
var offYouGo = getReady('daddy-map');
offYouGo('milk', 'mum');

Denn wenn man eine sehr wichtige Information (die Karte) mitbringt, ist man ausreichend informiert, um andere ähnliche Aktionen durchzuführen:

offYouGo('potatoes', 'great mum');

Für einen Entwickler würde ich eine Parallele ziehen zwischen Schließungen und OOP . Die Anfangsphase ist vergleichbar mit der Übergabe von Argumenten an einen Konstruktor in einer traditionellen OO-Sprache; die Handlungsphase ist letztendlich die Methode, die Sie aufrufen, um das zu erreichen, was Sie wollen. Und die Methode hat Zugriff auf diese Init-Argumente über einen Mechanismus namens Verschluss .

Siehe meine andere Antwort, die die Parallelität zwischen OO und Closures illustriert:

Wie erstellt man "richtig" ein benutzerdefiniertes Objekt in JavaScript?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X