JavaScript Closures Scope
JavaScript Closures und Scope verstehen
Closures sind eines der mächtigsten Konzepte in JavaScript. Lernen Sie Scope-Regeln und praktische Closure-Patterns.
Scope-Arten in JavaScript
| Scope | Beschreibung | Deklaration |
|---|---|---|
| Global | Überall verfügbar | Außerhalb von Funktionen |
| Function | Nur in der Funktion | var, function |
| Block | Nur im Block {} | let, const |
| Module | Nur im Modul | ES6 Modules |
Lexical Scope
// JavaScript verwendet LEXICAL SCOPE (auch: Static Scope)
// Der Scope wird zur SCHREIBZEIT festgelegt, nicht zur Laufzeit
const global = 'Ich bin global';
function outer() {
const outerVar = 'Ich bin in outer';
function inner() {
const innerVar = 'Ich bin in inner';
// inner kann auf alles zugreifen:
console.log(innerVar); // ✅ eigener Scope
console.log(outerVar); // ✅ äußerer Scope (Closure!)
console.log(global); // ✅ globaler Scope
}
inner();
// console.log(innerVar); // ❌ ReferenceError
}
outer();
// console.log(outerVar); // ❌ ReferenceError
Was ist eine Closure?
// Eine Closure entsteht, wenn eine Funktion auf Variablen
// aus ihrem äußeren Scope zugreift - auch nachdem die
// äußere Funktion bereits beendet ist!
function createCounter() {
let count = 0; // Diese Variable wird "eingeschlossen"
return function() {
count++; // Zugriff auf count aus äußerem Scope
return count;
};
}
const counter = createCounter();
// createCounter() ist BEENDET, aber count lebt weiter!
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count ist von außen nicht zugänglich!
// console.log(count); // ❌ ReferenceError
Closure für private Variablen
function createBankAccount(initialBalance) {
let balance = initialBalance; // Private Variable
const transactions = []; // Private Array
return {
deposit(amount) {
if (amount > 0) {
balance += amount;
transactions.push({ type: 'deposit', amount });
return balance;
}
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
transactions.push({ type: 'withdraw', amount });
return balance;
}
return 'Nicht genug Guthaben';
},
getBalance() {
return balance; // Nur lesen, nicht direkt ändern
},
getTransactions() {
return [...transactions]; // Kopie zurückgeben
}
};
}
const account = createBankAccount(100);
account.deposit(50); // 150
account.withdraw(30); // 120
account.getBalance(); // 120
// Direkt auf balance zugreifen? UNMÖGLICH!
// account.balance = 1000000; // Erstellt nur neue Property
// account.getBalance(); // Immer noch 120
Das klassische Loop-Problem
// ❌ PROBLEM mit var
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // Gibt 3, 3, 3 aus!
}, 100);
}
// var ist function-scoped, nicht block-scoped
// Alle Callbacks referenzieren dasselbe i
// ✅ LÖSUNG 1: let verwenden
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // Gibt 0, 1, 2 aus!
}, 100);
}
// let ist block-scoped, jede Iteration hat eigenes i
// ✅ LÖSUNG 2: IIFE (Immediately Invoked Function Expression)
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => {
console.log(j); // Gibt 0, 1, 2 aus!
}, 100);
})(i); // i wird als j "eingefangen"
}
Praktische Closure-Patterns
// 1. MEMOIZATION - Ergebnisse cachen
function memoize(fn) {
const cache = {}; // Closure über cache
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log('Aus Cache:', key);
return cache[key];
}
const result = fn.apply(this, args);
cache[key] = result;
return result;
};
}
const expensiveCalc = memoize((n) => {
console.log('Berechne...');
return n * n;
});
expensiveCalc(5); // "Berechne..." → 25
expensiveCalc(5); // "Aus Cache:" → 25 (keine Berechnung)
// 2. FUNCTION FACTORY
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
double(5); // 10
triple(5); // 15
// 3. ONCE - Funktion nur einmal ausführen
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
}
return result;
};
}
const initialize = once(() => {
console.log('Initialisierung...');
return 'Fertig';
});
initialize(); // "Initialisierung..." → "Fertig"
initialize(); // → "Fertig" (keine erneute Ausführung)
// 4. DEBOUNCE - Verzögerte Ausführung
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const searchInput = debounce((query) => {
console.log('Suche nach:', query);
// API Call...
}, 300);
// Wird nur einmal ausgeführt (nach 300ms Pause)
searchInput('H');
searchInput('He');
searchInput('Hel');
searchInput('Hell');
searchInput('Hello'); // Nur dieser wird ausgeführt
Closure-Fallen vermeiden
// ❌ Memory Leak durch Closure
function createHandler() {
const hugeData = new Array(1000000).fill('x');
return function handler() {
// hugeData wird nie freigegeben!
console.log('Handler aufgerufen');
};
}
// ✅ Besser: Nur benötigte Daten einschließen
function createHandler() {
const hugeData = new Array(1000000).fill('x');
const neededValue = hugeData.length; // Nur das Nötige
return function handler() {
console.log('Länge:', neededValue);
};
}
// ❌ Unbeabsichtigte Closure
function setupButtons() {
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() {
console.log('Button', i); // Immer letzter Index!
};
}
}
// ✅ Richtig mit let oder bind
function setupButtons() {
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() {
console.log('Button', i); // Korrekter Index
};
}
}
💡 Merke:
Eine Closure ist eine Funktion plus ihre lexikalische Umgebung. Sie ermöglicht private Variablen, State-Management und viele nützliche Patterns. Achten Sie auf Memory Leaks bei großen eingeschlossenen Objekten!