mirror of
https://codeberg.org/puppe/mpuppe.de-blog-posts.git
synced 2025-12-20 01:12:17 +01:00
182 lines
4.9 KiB
Markdown
182 lines
4.9 KiB
Markdown
|
|
---
|
||
|
|
layout: post
|
||
|
|
title: "Closures in Javascript (und Scala)"
|
||
|
|
date: 2012-08-03 21:37
|
||
|
|
comments: true
|
||
|
|
categories: Javascript
|
||
|
|
keywords: "closures, javascript, scala"
|
||
|
|
---
|
||
|
|
|
||
|
|
Closures[^terminologie] sind meiner Meinung nach eines der
|
||
|
|
interessantesten Sprachkonstrukte von Javascript. Sie erlauben sehr
|
||
|
|
elegante Lösungen, die sonst nicht möglich wären. Was das konkret
|
||
|
|
bedeutet, werde ich an einem Beispiel erläutern. Zunächst möchte ich
|
||
|
|
aber erklären, was Closures sind.
|
||
|
|
|
||
|
|
## Was ist eine Closure?
|
||
|
|
|
||
|
|
**Eine Closure beinhaltet eine Funktion und (auch nicht-lokale)
|
||
|
|
Variablen, die innerhalb der Funktion referenziert werden.** Closures
|
||
|
|
verändern die Lebensdauer von Variablen. Schauen wir uns dazu ein
|
||
|
|
Beispiel an:
|
||
|
|
|
||
|
|
``` javascript
|
||
|
|
var x = 0;
|
||
|
|
|
||
|
|
function foo() {
|
||
|
|
var y = 42;
|
||
|
|
return x + y;
|
||
|
|
}
|
||
|
|
|
||
|
|
x = x + 1;
|
||
|
|
```
|
||
|
|
|
||
|
|
Wir sehen hier zwei Variablen, `x` und `y`. Im Hinblick auf die
|
||
|
|
Sichtbarkeit unterscheiden sich die Variablen darin, dass `y` nur
|
||
|
|
*innerhalb* der Funktion `foo` sichtbar ist. Was die Lebensdauer
|
||
|
|
betrifft, so existiert `y` nur während `foo` ausgeführt wird.
|
||
|
|
|
||
|
|
Sichtbarkeit und Lebensdauer für sich genommen sind keine schwierigen
|
||
|
|
Konzepte. Javascript ist allerdings eine funktionale Sprache und da wird
|
||
|
|
die Sache interessant. Javascript erlaubt es nämlich, Funktionen als
|
||
|
|
Rückgabewert anderer Funktionen zu verwenden. Dazu erweitern wir das
|
||
|
|
obige Beispiel.
|
||
|
|
|
||
|
|
``` javascript
|
||
|
|
function makeFoo() {
|
||
|
|
var x = 0;
|
||
|
|
|
||
|
|
function foo() {
|
||
|
|
var y = 41;
|
||
|
|
x = x + 1;
|
||
|
|
return x + y;
|
||
|
|
}
|
||
|
|
|
||
|
|
return foo;
|
||
|
|
}
|
||
|
|
|
||
|
|
var bar = makeFoo();
|
||
|
|
console.log(bar());
|
||
|
|
console.log(bar());
|
||
|
|
console.log(bar());
|
||
|
|
```
|
||
|
|
|
||
|
|
In der Konsole erhält man folgende Ausgabe:
|
||
|
|
|
||
|
|
```
|
||
|
|
42
|
||
|
|
43
|
||
|
|
44
|
||
|
|
```
|
||
|
|
|
||
|
|
Das ist auf den ersten Blick überraschend, wenn wir davon ausgehen, dass
|
||
|
|
lokale Variablen nur existieren, solange die Funktion, in der sie
|
||
|
|
definiert wurden, ausgeführt wird. Die Variable `x` hingegen bleibt
|
||
|
|
weiterhin innerhalb der Funktion `foo` bzw. `bar` sichtbar und behält
|
||
|
|
zudem ihren Zustand, obwohl `makeFoo` vollständig abgearbeitet wurde.
|
||
|
|
*`foo` wurde zusammen mit `x` in eine Closure gepackt*.
|
||
|
|
|
||
|
|
## Praktische Anwendung
|
||
|
|
|
||
|
|
Das obige Beispiel ist konstruiert und in der Praxis kaum nützlich.
|
||
|
|
Closures haben aber durchaus sinnvolle Anwendungen. Stellen wir uns
|
||
|
|
einmal vor, wir wollten verschiedene Objekte mit einer eindeutigen
|
||
|
|
Nummer (einer ID) versehen. Dazu wollen wir eine Funktion `generateId`
|
||
|
|
definieren, die stets eine andere Zahl zurückliefert. Der Einfachheit
|
||
|
|
halber zählen wir von 0 aufwärts. Wir haben mehrere Möglichkeiten dieses
|
||
|
|
Problem zu lösen.
|
||
|
|
|
||
|
|
### Möglichkeit 1: Globaler Zähler
|
||
|
|
|
||
|
|
``` javascript
|
||
|
|
var counter = 0;
|
||
|
|
|
||
|
|
function generateId() {
|
||
|
|
var id = counter;
|
||
|
|
counter = counter + 1;
|
||
|
|
return id;
|
||
|
|
}
|
||
|
|
|
||
|
|
var obj = {
|
||
|
|
id: generateId()
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Ich halte diese Lösung für nicht sehr sinnvoll. `counter` ist eine
|
||
|
|
globale Variable. D.h. wir verschmutzen hier den globalen Namensraum mit
|
||
|
|
einer Variable, die nur für eine einzige Funktion benötigt wird.
|
||
|
|
Außerdem kann die Variable praktisch an jeder Stelle im Programm
|
||
|
|
versehentlich geändert werden. Und es wird überhaupt nicht klar, dass
|
||
|
|
counter` und `generateId` zusammengehören.
|
||
|
|
|
||
|
|
### Möglichkeit 2: Zähler und Funktion in einem Objekt
|
||
|
|
|
||
|
|
``` javascript
|
||
|
|
var idGenerator = {
|
||
|
|
counter: 0,
|
||
|
|
generateId: function() {
|
||
|
|
var id = this.counter;
|
||
|
|
this.counter = this.counter + 1;
|
||
|
|
return id;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var obj = {
|
||
|
|
id: idGenerator.generateId()
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Diese Lösung ist schon etwas besser, in der Hinsicht, dass wir auf eine
|
||
|
|
globale Variable `counter` verzichten. Auch wird der Zusammenhang von
|
||
|
|
`counter` und `generateId` deutlicher. Allerdings ist diese Variante
|
||
|
|
unhandlich in der Verwendung (`idGenerator.generateId()`). Außerdem kann
|
||
|
|
auch hier `counter` an anderer Stelle im Programm geändert werden,
|
||
|
|
obwohl die Variable einzig und allein von der Funktion `generateId`
|
||
|
|
manipuliert werden sollte.
|
||
|
|
|
||
|
|
### Möglichkeit 3: Closure
|
||
|
|
|
||
|
|
``` javascript
|
||
|
|
var generateId = (function() {
|
||
|
|
var counter = 0;
|
||
|
|
return function() {
|
||
|
|
var id = counter;
|
||
|
|
counter = counter + 1;
|
||
|
|
return id;
|
||
|
|
};
|
||
|
|
})();
|
||
|
|
|
||
|
|
var obj = {
|
||
|
|
id: generateId()
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Ich halte diese Lösung für die eleganteste. Der Zusammenhang von
|
||
|
|
`counter` und `generateId` wird deutlich und der Zähler ist außerhalb
|
||
|
|
der Funktion überhaupt nicht sichtbar.
|
||
|
|
|
||
|
|
## Anhang: Das Beispiel in Scala
|
||
|
|
|
||
|
|
Closures gibt es nicht nur in Javascript, sondern in wohl jeder
|
||
|
|
funktionalen Programmiersprache. Ich habe mich kürzlich im Rahmen einer
|
||
|
|
Vorlesung mit Scala beschäftigen dürfen, daher hier das obige Beispiel
|
||
|
|
in Scala:
|
||
|
|
|
||
|
|
``` scala
|
||
|
|
val generateId = (() => {
|
||
|
|
var counter = 0
|
||
|
|
() => {
|
||
|
|
var id = counter;
|
||
|
|
counter = counter + 1;
|
||
|
|
id
|
||
|
|
}
|
||
|
|
}).apply()
|
||
|
|
```
|
||
|
|
|
||
|
|
[^terminologie]:
|
||
|
|
Kurz zur Terminologie: Das deutsche Wort für „function
|
||
|
|
closure“ ist wohl „Funktionsabschluss“. Allerdings hat
|
||
|
|
man wenig Glück, wenn man danach googlet. Mir scheint der Begriff nicht
|
||
|
|
sehr gebräuchlich zu sein, also bleibe ich bei der englischen
|
||
|
|
Bezeichnung.
|