MDN wants to learn about developers like you: https://qsurvey.mozilla.com/s3/d6d7ff2e2f9c

Diese Übersetzung ist unvollständig. Bitte helfen Sie, diesen Artikel aus dem Englischen zu übersetzen.


 

Closures sind Funktionen mit unabhängigen, freien Variablen. Anders ausgedrückt: Die in der Closure definierte Funktion merkt sich die Umgebung, in der sie erzeugt wurde.

Lexical scoping

Lexikalischer Sichtbarkeitsbereich

Betrachten wir folgendes Beispiel:

function init() {
  var name = "Mozilla"; // name ist eine von init erzeugte lokale Variable
  function displayName() { // displayName() ist die innere, umschlossene Funktion, sprich die 'Closure'
    alert(name); // hier wird die in der umschließenden Eltern-Funktion deklarierte Variable benutzt    
  }
  displayName();    
}
init();

init() erzeugt eine lokale Variable name und anschließend eine Funktion displayName(). displayName() ist eine interne Funktion definiert in init() und ist ausschließlich im Rahmen dieser Funktion verfügbar. displayName() besitzt keine eigenen lokalen Variablen, hat jedoch Zugriff auf Variablen umgebender Funktionen und kann darum die Variable name aus der umschließenden Eltern-Funktion abrufen.

Dieses Code-Beispiel zeigt, dass es funktioniert. Es handelt sich hier um einen lexikalischen Sichtbarkeitsbereich (lexical scoping), d.h. in JavaScript wird der Sichtbarkeitsbereich (scope) einer Variablen bestimmt durch ihren Fundort im Quellcode (darum lexikalisch). Und verschachtelte Funktionen haben Zugriff auf Variablen, die im umschließenden Sichtbarkeitsbereich (outer scope) deklariert werden.

Closure

Funktionsabschluss

Betrachten wir folgendes Beispiel:

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

Zum Laufen gebracht zeigt dieser Code exakt den gleichen Effekt wie das vorherige init() Beispiel: Die Zeichenfolge "Mozilla" wird als JavaScript alert Meldung angezeigt. Der interessante Unterschied besteht darin, dass die innere Funktion displayName() von der äußeren Funktion schon vor ihrer Ausführung zurückgegeben wurde.

Dass der Code weiterhin funktioniert, mag verwundern. Üblicherweise existieren die in einer Funktion enthaltenen lokalen Variablen nur während ihrer Ausführung. Man könnte also annehmen, dass die Variable name nicht mehr abrufbar ist, nachdem makeFunc() geendet hat. Da der Code trotzdem funktioniert, ist dies offensichtlich nicht der Fall.

Des Rätsels Lösung ist, dass myFunc zu einem Funktionsabschluss, einer Closure, geworden ist. Die Closure ist ein besonderes Objekt mit zwei außergewöhnlichen Merkmalen – sie umfasst sowohl die Funktion, wie auch das Umfeld, in dem diese erzeugt wurde. Dieses Umfeld besteht aus sämtlichen lokalen Variablen, die im Geltungsbereich (Scope) lagen zum Zeitpunkt der Erzeugung der Closure. Im obigen Beispiel ist myFunc eine Closure, die sowohl die Funktion myFunc, wie auch die Zeichenfolge "Mozilla", die bei der Erzeugung der Closure exisitierte, umfasst. 

Hier nun ein etwas interessanteres Beispiel, eine makeAdder() Funktion:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

In diesem Beispiel haben wir eine Funktion makeAdder(x) definiert, die ein einzelnes Argument x erwartet und eine neue Funktion zurück gibt. Die zurückgegebene Funktion erwartet ein Argument y und liefert die Summe von x und y als Ergebnis.

Die Funktion makeAdder ist im Wesentlichen eine Factory für spezielle Funktionen: sie erzeugt  Funktionen, welche ihrem Argument einen bestimmten Wert hinzuaddieren. Im vorangegangenen Beispiel erzeugen wir mittels dieser Factory zwei neue Funktionen: eine von ihnen addiert 5 zu ihrem Argument, die andere addiert 10 hinzu.

add5 und add10 sind beides Closures. They share the same function body definition, but store different environments. In add5's environment, x is 5. As far as add10 is concerned, x is 10.

Closures in der Praxis

Soviel zur Theorie von Closures — aber gibt es tatsächlich einen Praxisnutzen? Betrachten wir dafür die Auswirkungen in der Praxis. Ein Closure lässt uns Daten (die Ausführungsumgebung) mit einer Funktion, die auf diesen Daten operiert, verbinden. Das hat offensichtliche parallelen zur objektorientierten Programmierung, bei der die Daten eines Objekts (die Objekteigenschaften) mit einer oder mehreren Methoden verbunden werden. Somit kann ein Closure immer dann verwendet werden, wo normalerweise ein Objekt mit einer einzigen Methode verwendet wird.

Besonders im Web kommt uns das häufig entgegen. Ein Großteil von JavaScript-Code ist ereignisbasierter Code: wir definieren erst ein Verhalten, und hängen es dann ein Benutzerereignis (bspw. einen Mausklick oder Tastendruck). Unser Code nimmt dabei üblicherweise die Form eines Callback an: eine einzelne Funktion, die bei Eintreten des Ereignisses aufgerufen wird.

Here's a practical example: suppose we wish to add some buttons to a page that adjust the text size. One way of doing this is to specify the font-size of the body element in pixels, then set the size of the other elements on the page (such as headers) using the relative em unit:

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

Our interactive text size buttons can change the font-size property of the body element, and the adjustments will be picked up by other elements on the page thanks to the relative units.

Here's the JavaScript:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12, size14, and size16 are now functions which will resize the body text to 12, 14, and 16 pixels, respectively. We can attach them to buttons (in this case links) as follows:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
12
14
16 

Emulating private methods with closures

Languages such as Java provide the ability to declare methods private, meaning that they can only be called by other methods in the same class.

JavaScript does not provide a native way of doing this, but it is possible to emulate private methods using closures. Private methods aren't just useful for restricting access to code: they also provide a powerful way of managing your global namespace, keeping non-essential methods from cluttering up the public interface to your code.

Here's how to define some public functions that can access private functions and variables, using closures which is also known as the module pattern:

var counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };   
})();

console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1

There's a lot going on here. In previous examples each closure has had its own environment; here we create a single environment which is shared by three functions: counter.increment, counter.decrement, and counter.value.

The shared environment is created in the body of an anonymous function, which is executed as soon as it has been defined. The environment contains two private items: a variable called privateCounter and a function called changeBy. Neither of these private items can be accessed directly from outside the anonymous function. Instead, they must be accessed by the three public functions that are returned from the anonymous wrapper.

Those three public functions are closures that share the same environment. Thanks to JavaScript's lexical scoping, they each have access to the privateCounter variable and changeBy function.

You'll notice we're defining an anonymous function that creates a counter, and then we call it immediately and assign the result to the counter variable. We could store this function in a separate variable makeCounter and use it to create several counters.

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* Alerts 0 */
counter1.increment();
counter1.increment();
alert(counter1.value()); /* Alerts 2 */
counter1.decrement();
alert(counter1.value()); /* Alerts 1 */
alert(counter2.value()); /* Alerts 0 */

Notice how each of the two counters maintains its independence from the other. Its environment during the call of the makeCounter() function is different each time. The closure variable privateCounter contains a different instance each time.

Using closures in this way provides a number of benefits that are normally associated with object oriented programming, in particular data hiding and encapsulation.

Creating closures in loops: A common mistake

Prior to the introduction of the let keyword in ECMAScript 6, a common problem with closures occurred when they were created inside a loop. Consider the following example:

 

Helpful notes will appear here

E-mail:

Name:

Age:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp(); 

The helpText array defines three helpful hints, each associated with the ID of an input field in the document. The loop cycles through these definitions, hooking up an onfocus event to each one that shows the associated help method.

If you try this code out, you'll see that it doesn't work as expected. No matter what field you focus on, the message about your age will be displayed.

The reason for this is that the functions assigned to onfocus are closures; they consist of the function definition and the captured environment from the setupHelp function's scope. Three closures have been created, but each one shares the same single environment. By the time the onfocus callbacks are executed, the loop has run its course and the item variable (shared by all three closures) has been left pointing to the last entry in the helpText list.

One solution in this case is to use more closures: in particular, to use a function factory as described earlier on:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp(); 

This works as expected. Rather than the callbacks all sharing a single environment, the makeHelpCallback function creates a new environment for each one in which help refers to the corresponding string from the helpText array.

Performance considerations

It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.

For instance, when creating a new object/class, methods should normally be associated to the object's prototype rather than defined into the object constructor. The reason is that whenever the constructor is called, the methods would get reassigned (that is, for every object creation).

Consider the following impractical but demonstrative case:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

The previous code does not take advantage of the benefits of closures and thus could instead be formulated:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

However, redefining the prototype is not recommended, so the following example is even better because it appends to the existing prototype:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

The above code can also be written in a cleaner way with the same result:

function MyObject(name, message) {
    this.name = name.toString();
    this.message = message.toString();
}
(function() {
    this.getName = function() {
        return this.name;
    };
    this.getMessage = function() {
        return this.message;
    };
}).call(MyObject.prototype);

In the two previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See Details of the Object Model for more details.

Schlagwörter des Dokuments und Mitwirkende

Schlagwörter: 
 Zuletzt aktualisiert von: cami,