Ein Hiker benötigt verschiedene Vorräte wie Essen, Wasser und Kleidung, um seine Wanderung durchzuführen. Der Rucksack enthält alle notwendigen Vorräte. Die Vorräte sind jederzeit leicht zugänglich für den Wanderer während seiner Wanderung.

Diese Analogie lässt sich auf Closures in JavaScript übertragen. In JavaScript ermöglicht ein Closure das Speichern von Daten in einem "Rucksack", der von einer Funktion erstellt wird.

Der "Rucksack" enthält alle Variablen, die in der äußeren Funktion definiert wurden. Diese können von der inneren Funktion, die innerhalb des Closures definiert ist, jederzeit aufgerufen und verwendet werden.

Ein Wanderer kann während der Wanderung leicht auf seine Vorräte zugreifen. So kann auch die innere Funktion auf die im Closure gespeicherten Variablen zugreifen und diese bei Bedarf verwenden.

Warum sollte ich etwas über "Closure" lernen?

  • Closure ist ein grundlegendes Konzept in JavaScript. Mit diesem können private Variablen, Funktionen und Methoden erstellt werden. So lassen sich die Modularität, Wartbarkeit und Sicherheit des Codes verbessern.
  • Closure wird häufig in JavaScript-Frameworks und -Bibliotheken wie React, Angular und jQuery verwendet. Es ist ein wesentliches Feature für die Erstellung skalierbarer und komplexer Anwendungen.
  • Closure stammt aus der funktionalen Programmierung, die in der modernen Webentwicklung immer beliebter wird.
  • Das Verständnis von Closure kann helfen, effizienteren Code zu schreiben. So werden unnötige Funktionsaufrufe vermieden und die Menge an doppeltem Code reduziert.
  • Durch die Verwendung von Closure kann besser wiederverwendbarer und zerlegbarer Code erstellt werden. Das kann auf lange Sicht Zeit und Mühe sparen.
  • Closure ist ein leistungsfähiges Werkzeug für die Verwaltung asynchroner Aufgaben und den Umgang mit event-driven programming. Diese sind für die Erstellung moderner Webanwendungen unerlässlich.

Definition von "Closure"

Erinnerung: Was bedeutet "Scope"?

  • Der Scope verwaltet den Gültigkeitsbereich von Variablen.
  • Innerhalb eines Scopes ist der Zugriff auf die darin definierte Variable möglich. Außerhalb des Scopes ist die Variable nicht mehr zugänglich.

Bevor wir mit einem Beispiel beginnen, sollten wir uns einige Definitionen anschauen:

  1. Global Memory: Global Memory ist ein Speicherbereich, in dem Variablen und Funktionen gespeichert werden. In JavaScript umfasst der globale Speicher alle globalen Variablen und Funktionen, die im Programm definiert sind.
  2. Execution Context: Ein Execution Context ist ein Objekt, das Informationen über den aktuellen Ausführungszustand des Programms enthält. Der Execution Context umfasst Variablen, Funktionen, Argumente und andere Informationen, die während der Ausführung des Codes benötigt werden. Jede Funktion hat ihren eigenen Execution Context.

Der Execution Context wird erstellt, wenn eine Funktion aufgerufen wird. Sie enthält Informationen darüber, wo die Funktion aufgerufen wurde und welche Variablen und Argumente der Funktion übergeben wurden.

  1. Call Stack: Der Call Stack ist ein Stapel, der alle Ausführungskontexte speichert, die derzeit aktiv sind. Wenn eine Funktion aufgerufen wird, wird ein neuer Ausführungskontext erstellt und auf den Aufrufstapel gelegt.

Der Aufrufstapel ermöglicht es, dass die Funktionen rekursiv aufzurufen, ohne dass die zuvor aufgerufenen Funktionen vergessen werden. Was passiert, wenn die Funktion beendet ist? Dann wird der Ausführungskontext vom Stapel entfernt. Weiterhin wird die Ausführung an die Stelle zurückgegeben, an der die Funktion aufgerufen wurde.

Lass uns folgendes Beispiel Zeile für Zeile durchgehen:

function counter() {
  var count = 0;
  function increment() {
    count++;
    console.log(count);
  }
  return increment;
}
var myCounter = counter();
myCounter(); // Output: 1
myCounter(); // Output: 2
  • Javascript-Closures-Step1
  • Javascript-Closures-Step2
  • Javascript-Closures-Step3
  • Javascript-Closures-Step4
  • Javascript-Closures-Step5
  • Javascript-Closures-Step6
  • Javascript-Closures-Step7
  • Javascript-Closures-Step8
  • Javascript-Closures-Step9
increment.scopes[0].Closure

Werfen wir nun einen Blick auf myCounter in der Konsole. Beachte das Scopes -> Closure Member. Dieser Wert ist natürlich nicht von außen über "increment.scopes[0].Closure" zugänglich, da er ein Hidden Member ist.

Power of Closure

Hier sind einige wichtige Anwendungen von Closure in JavaScript:

  • Erstellen von privaten Variablen und Funktionen:Closure ermöglicht die Erstellung von privaten Variablen und Funktionen, die von außen nicht zugänglich sind. Dies kann helfen, die Sicherheit und Modularität des Codes zu verbessern. Die Verwendung privater Variablen mit Closure kann dazu beitragen, den versehentlichen oder absichtlichen Zugriff auf sensible Daten durch andere Teile des Codes zu verhindern.
export function checkUserAccess(userId) {
  let hasAccess = false;
 
  hasAccess = fetchAccess();
 
  // Return a function that can be used to check access without exposing implementation details
  return function () {
    return hasAccess;
  };
}
  • Vermeidung von globalen Variablen: Closure ermöglicht die Vermeidung von globalen Variablen, die oft zu Verwirrung und Fehlern führen können. Stattdessen können Variablen und Funktionen in einem enger definierten Bereich deklariert werden. Im obigen checkUserAccess-Beispiel bleibt die Variable hasAccess im Funktions-Scope, ohne dass der globale Scope "verschmutzt" wird.
  • Caching: Closure ermöglicht das Caching von Werten in einer Funktion, um die Leistung zu verbessern. Jede Instanz der Funktion erhält dabei ihre eigene Cache-Speicherung, die von anderen Instanzen nicht beeinträchtigt wird. Durch die Verwendung von Closure können Daten zwischengespeichert werden. So werden wiederholte teure Berechnungen vermieden und die Leistung von Funktionen verbessert.
export function createExpensiveCalculation() {
  // Without closure, we would need to define a global cache and keep track of each
  // cache manually, which can be error-prone and difficult to maintain
  const cache = new Map();
 
  return function (n: number) {
    if (cache.has(n)) {
      return cache.get(n);
    }
 
    const result = n * n;
    cache.set(n, result);
    return result;
  };
}
 
// Each instance of createExpensiveCalculation gets its own cache
const expensiveCalculation1 = createExpensiveCalculation();
const expensiveCalculation2 = createExpensiveCalculation();
 
console.log(expensiveCalculation1(2)); // 4
console.log(expensiveCalculation1(2)); // 4 (cached)
console.log(expensiveCalculation1(3)); // 9
console.log(expensiveCalculation1(3)); // 9 (cached)
 
console.log(expensiveCalculation2(2)); // 4
console.log(expensiveCalculation2(2)); // 4 (cached)
console.log(expensiveCalculation2(4)); // 16
console.log(expensiveCalculation2(4)); // 16 (cached)
  • ​​​​​Verwendung in asynchronem Code: Closure kann bei der Verwaltung von asynchronem Code und Event-Handling helfen. Dieser ermöglicht es, Zustände über mehrere Aufrufe hinweg beizubehalten.
  • Implementierung von Callback-Funktionen: Closure ist oft bei der Implementierung von Callback-Funktionen erforderlich. Denn diese Funktionen benötigen Zugriff auf Variablen, die im Kontext der aufrufenden Funktion definiert sind.
function debounce(fn, delayCallback) {
  let timeoutId;
  // closure starts here, with the nested function
  return function (...args) {
    const context = this;
    clearTimeout(timeoutId);
    // the setTimeout function is called inside the nested function,
    // which means it has access to the timeoutId variable in its outer scope
    timeoutId = setTimeout(() => {
      fn.apply(context, args);
    }, delayCallback);
  };
}
  • Reusability: Closure ermöglicht die Entkopplung zwischen der ursprünglichen Funktion und der neuen Funktion. So sind Änderungen an der ursprünglichen Funktion möglich, ohne Auswirkungen auf den Code, der auf die neue Funktion angewiesen ist. Siehe das folgende Beispiel...
...mit Closure:
export function addErrorLogging(fn) {
  // returns a new function that wraps the original function and handles errors
  return function (...args) {
    try {
      return fn.apply(this, args);
    } catch (error) {
      // logs the error message
      console.error(`Error occurred: ${error.message}`);
      // re-throws the error to propagate it
      throw error;
    }
  };
}
 
export function divide(a, b) {
  if (b === 0) {
    throw new Error("Cannot divide by zero.");
  }
  return a / b;
}
 
export const loggedDivide = addErrorLogging(divide);
...ohne Closure:
export function addErrorLogging(fn) {
  const args = arguments
     try { 
       return fn.apply(this, args);
     } catch (error) {
       console.error(`Error occurred: ${error.message}`);
       throw error;
     }
 }
  
 export function divide(a, b) {
   if (b === 0) {
     throw new Error("Cannot divide by zero.");
   }
    console.log('result: ' + a / b)
   return a / b;
 }
  
/**
 * loggedDivide is tightly coupled to divide. If the implementation details of divide changes,
 * then usage of loggedDivide has to be changed.
 * In addition, loggedDivide is not reuseable with other parameters.
 */
const loggedDivide = addErrorLogging(divide.bind(null, 10, 2));

Die Nachteile der Verwendung von Closures

Memory Leak: Warum können Closures zu Speicherproblemen führen? Sie halten Variablen und Funktionen im Speicher, auch wenn sie nicht mehr benötigt werden. Hier ist wichtig sicherzustellen, dass es eine Möglichkeit gibt, die restlichen Daten in Closures zu löschen. Falls die Gefahr besteht, dass der Garbage Collector den verwendeten Speicher aufgrund bestehender Referenzen nicht selber löschen kann. Siehe Funktion unsubscribe() im nächsten Beispiel:

function dataApi() {
  const data = [];
 
  function fetchData() {
    data.push(createLargeObject());
  }
 
  function unsubscribe() {
    data.length = 0;
  }
 
  return {
    fetchData,
    unsubscribe,
  };
}
 
const dataFromApi = dataApi();
 
function createLargeObject() {
  let obj = {};
  for (let i = 0; i < 10000; i++) {
    obj[i] = Math.random().toString(36).substring(2, 15);
  }
  return obj;
}
  • Komplexität: Closures können die Komplexität des Codes erhöhen. Insbesondere wenn sie verschachtelt werden und mehrere Ebenen von Funktionalität umfassen.
  • Sicherheitsrisiko: Closures können ein Sicherheitsrisiko darstellen, da sie auf Variablen außerhalb ihrer eigenen Funktion zugreifen können. Wenn diese Variablen sensibel sind, kann dies zu Sicherheitsproblemen führen. Die Entwickler*innen sollten darauf achten, wie viel von der internen Logik über das Closure offengelegt wird.

Fazit

Closure bietet in JavaScript viele Vorteile. Es ermöglicht uns, Daten und Funktionalitäten sicher und effektiv zu kapseln. Dies führt zu einer besseren Code-Wartbarkeit und -Lesbarkeit. Einige der Vorteile von Closure sind:

  • Vermeidung von globalen Variablen
  • Reduzierung von Redundanz im Code
  • Verwendung in asynchronem Code und Event-Handling
  • Verwendung in Higher-Order-Funktionen
  • Schutz von privaten Daten und Funktionen

Jedoch sollten Entwickler*innen auch einige Dinge beachten, um zu vermeiden, dass Closure zu unerwarteten Fehlern führt.

Beispielsweise sollten Entwickler*innen darauf achten, dass sie Closure verwenden, ohne die Lesbarkeit des Codes zu beeinträchtigen. Weiterhin sollten sie es meiden, sensiblen Daten über Closure verfügbar machen.

Ebenfalls ist zu beachten, dass Closures nicht zu unnötiger Redundanz im Code führen. Und zuletzt ist es ratsam, Clousures sie nicht übermäßig zu verwenden, um die Leistung nicht zu beeinträchtigen.

Insgesamt ist Closure ein mächtiges Feature in JavaScript:

Dieses kann Entwickler*innen dabei helfen, besseren und sichereren Code verantwortungsbewusster und effektiver Verwendung zu schreiben.

SCHREIB UNS

* Pflichtfeld

SCHREIB UNS

* Pflichtfeld

Cookie-Einstellungen

Diese Website verwendet Cookies, um Inhalte und Anzeigen zu personalisieren, Funktionen für soziale Medien anbieten zu können und Zugriffe auf die Website zu analysieren. Zudem werden Informationen zu Ihrer Verwendung der Website an Partner für soziale Medien, Werbung und Analysen weitergegeben. Die Partner führen diese Informationen möglicherweise mit weiteren Daten zusammen, die Sie ihnen bereitgestellt haben oder die sie im Rahmen Ihrer Nutzung der Dienste gesammelt haben.

Weitere Informationen finden Sie in unserer Datenschutzerklärung. Dort können Sie nachträglich auch Ihre Cookie-Einstellungen ändern.

contact icon

Kontakt aufnehmen