Scopes und Closures sind wichtig in JavaScript. Aber sie waren für mich verwirrend, als ich anfing. Hier ist eine Erklärung von Scopes und Closures, die Ihnen helfen soll, zu verstehen, was sie sind.
Beginnen wir mit Scopes.
Scope
Ein Scope in JavaScript definiert, auf welche Variablen Sie Zugriff haben. Es gibt zwei Arten von Scopes – globaler Scope und lokaler Scope.
Globaler Scope
Wenn eine Variable außerhalb aller Funktionen oder geschweiften Klammern ({}) deklariert wird, wird sie als im **globalen Scope** definiert bezeichnet.
Dies gilt nur für JavaScript in Webbrowsern. In Node.js deklarieren Sie globale Variablen anders, aber wir werden in diesem Artikel nicht auf Node.js eingehen.
const globalVariable = 'some value'
Sobald Sie eine globale Variable deklariert haben, können Sie diese Variable überall in Ihrem Code verwenden, auch in Funktionen.
const hello = 'Hello CSS-Tricks Reader!'
function sayHello () {
console.log(hello)
}
console.log(hello) // 'Hello CSS-Tricks Reader!'
sayHello() // 'Hello CSS-Tricks Reader!'
Obwohl Sie Variablen im globalen Scope deklarieren können, wird davon abgeraten. Dies liegt daran, dass die Möglichkeit von Namenskollisionen besteht, bei denen zwei oder mehr Variablen denselben Namen haben. Wenn Sie Ihre Variablen mit const oder let deklarieren, erhalten Sie bei jeder Namenskollision einen Fehler. Das ist unerwünscht.
// Don't do this!
let thing = 'something'
let thing = 'something else' // Error, thing has already been declared
Wenn Sie Ihre Variablen mit var deklarieren, überschreibt Ihre zweite Variable die erste, nachdem sie deklariert wurde. Dies ist ebenfalls unerwünscht, da es Ihren Code schwer zu debuggen macht.
// Don't do this!
var thing = 'something'
var thing = 'something else' // perhaps somewhere totally different in your code
console.log(thing) // 'something else'
Deklarieren Sie also immer lokale Variablen, keine globalen Variablen.
Lokaler Scope
Variablen, die nur in einem bestimmten Teil Ihres Codes verwendbar sind, gelten als im lokalen Scope. Diese Variablen werden auch als **lokale Variablen** bezeichnet.
In JavaScript gibt es zwei Arten von lokalen Scopes: Funktions-Scope und Block-Scope.
Sprechen wir zuerst über Funktions-Scopes.
Funktions-Scope
Wenn Sie eine Variable in einer Funktion deklarieren, können Sie diese Variable nur innerhalb der Funktion aufrufen. Sobald Sie die Funktion verlassen, können Sie diese Variable nicht mehr abrufen.
Im folgenden Beispiel ist die Variable hello im Scope von sayHello.
function sayHello () {
const hello = 'Hello CSS-Tricks Reader!'
console.log(hello)
}
sayHello() // 'Hello CSS-Tricks Reader!'
console.log(hello) // Error, hello is not defined
Block-Scope
Wenn Sie eine Variable mit const oder let innerhalb einer geschweiften Klammer ({}) deklarieren, können Sie diese Variable nur innerhalb dieser geschweiften Klammer aufrufen.
Im folgenden Beispiel sehen Sie, dass hello dem Scope der geschweiften Klammern zugeordnet ist.
{
const hello = 'Hello CSS-Tricks Reader!'
console.log(hello) // 'Hello CSS-Tricks Reader!'
}
console.log(hello) // Error, hello is not defined
Der Block-Scope ist eine Untermenge des Funktions-Scopes, da Funktionen mit geschweiften Klammern deklariert werden müssen (es sei denn, Sie verwenden Arrow Functions mit implizitem Return).
Function Hoisting und Scopes
Funktionen, die mit einer Funktionsdeklaration deklariert werden, werden immer an den Anfang des aktuellen Scopes "gehoisted". Daher sind diese beiden gleichwertig.
// This is the same as the one below
sayHello()
function sayHello () {
console.log('Hello CSS-Tricks Reader!')
}
// This is the same as the code above
function sayHello () {
console.log('Hello CSS-Tricks Reader!')
}
sayHello()
Wenn Funktionen mit einem Funktionsausdruck deklariert werden, werden sie nicht an den Anfang des aktuellen Scopes gehoisted.
sayHello() // Error, sayHello is not defined
const sayHello = function () {
console.log(aFunction)
}
Aufgrund dieser beiden Variationen kann Function Hoisting potenziell verwirrend sein und sollte nicht verwendet werden. Deklarieren Sie Ihre Funktionen immer, bevor Sie sie verwenden.
Funktionen haben keinen Zugriff auf die Scopes anderer Funktionen
Funktionen haben keinen Zugriff auf die Scopes anderer Funktionen, wenn Sie sie separat definieren, auch wenn eine Funktion in einer anderen verwendet wird.
In diesem Beispiel unten hat second keinen Zugriff auf firstFunctionVariable.
function first () {
const firstFunctionVariable = `I'm part of first`
}
function second () {
first()
console.log(firstFunctionVariable) // Error, firstFunctionVariable is not defined
}
Verschachtelte Scopes
Wenn eine Funktion in einer anderen Funktion definiert ist, hat die innere Funktion Zugriff auf die Variablen der äußeren Funktion. Dieses Verhalten wird als **lexikalisches Scoping** bezeichnet.
Die äußere Funktion hat jedoch keinen Zugriff auf die Variablen der inneren Funktion.
function outerFunction () {
const outer = `I'm the outer function!`
function innerFunction() {
const inner = `I'm the inner function!`
console.log(outer) // I'm the outer function!
}
console.log(inner) // Error, inner is not defined
}
Um zu visualisieren, wie Scopes funktionieren, können Sie sich ein Einwegspiegel vorstellen. Sie können nach draußen sehen, aber Menschen von draußen können Sie nicht sehen.

Wenn Sie Scopes innerhalb von Scopes haben, stellen Sie sich mehrere Schichten von Einwegspiegeln vor.

Nachdem Sie nun alles über Scopes verstanden haben, sind Sie gut darauf vorbereitet, herauszufinden, was Closures sind.
Closures
Immer wenn Sie eine Funktion innerhalb einer anderen Funktion erstellen, haben Sie eine Closure erstellt. Die innere Funktion ist die Closure. Diese Closure wird normalerweise zurückgegeben, damit Sie die Variablen der äußeren Funktion später verwenden können.
function outerFunction () {
const outer = `I see the outer variable!`
function innerFunction() {
console.log(outer)
}
return innerFunction
}
outerFunction()() // I see the outer variable!
Da die innere Funktion zurückgegeben wird, können Sie den Code auch etwas kürzen, indem Sie eine return-Anweisung schreiben, während Sie die Funktion deklarieren.
function outerFunction () {
const outer = `I see the outer variable!`
return function innerFunction() {
console.log(outer)
}
}
outerFunction()() // I see the outer variable!
Da Closures Zugriff auf die Variablen der äußeren Funktion haben, werden sie normalerweise für zwei Dinge verwendet:
- Zur Steuerung von Seiteneffekten
- Zur Erstellung privater Variablen
Seiteneffekte mit Closures steuern
Seiteneffekte treten auf, wenn Sie etwas anderes tun, als einen Wert aus einer Funktion zurückzugeben. Viele Dinge können Seiteneffekte sein, wie eine Ajax-Anfrage, ein Timeout oder sogar eine console.log-Anweisung.
function (x) {
console.log('A console.log is a side effect!')
}
Wenn Sie Closures zur Steuerung von Seiteneffekten verwenden, sind Sie normalerweise an denen interessiert, die Ihren Programmfluss stören können, wie Ajax oder Timeouts.
Lassen Sie uns dies anhand eines Beispiels durchgehen, um die Dinge klarer zu machen.
Nehmen wir an, Sie möchten für den Geburtstag Ihres Freundes einen Kuchen backen. Dieser Kuchen würde eine Sekunde dauern, also haben Sie eine Funktion geschrieben, die nach einer Sekunde Kuchen gebacken ausgibt.
Ich verwende hier ES6 Arrow Functions, um das Beispiel kürzer und leichter verständlich zu machen.
function makeCake() {
setTimeout(_ => console.log(`Made a cake`), 1000)
}
Wie Sie sehen können, hat diese Kuchenbackfunktion einen Seiteneffekt: ein Timeout.
Nehmen wir weiter an, Sie möchten, dass Ihr Freund eine Geschmacksrichtung für den Kuchen wählt. Dazu können Sie Ihrer Funktion makeCake eine Geschmacksrichtung hinzufügen.
function makeCake(flavor) {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000)
}
Wenn Sie die Funktion ausführen, beachten Sie, dass der Kuchen sofort nach einer Sekunde gebacken wird.
makeCake('banana')
// Made a banana cake!
Das Problem hier ist, dass Sie den Kuchen nicht sofort nach Kenntnis der Geschmacksrichtung backen möchten. Sie möchten ihn später backen, wenn die Zeit reif ist.
Um dieses Problem zu lösen, können Sie eine Funktion prepareCake schreiben, die Ihre Geschmacksrichtung speichert. Geben Sie dann die Closure makeCake innerhalb von prepareCake zurück.
Von nun an können Sie die zurückgegebene Funktion jederzeit aufrufen, und der Kuchen wird innerhalb einer Sekunde gebacken.
function prepareCake (flavor) {
return function () {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000)
}
}
const makeCakeLater = prepareCake('banana')
// And later in your code...
makeCakeLater()
// Made a banana cake!
So werden Closures verwendet, um Seiteneffekte zu reduzieren – Sie erstellen eine Funktion, die die innere Closure nach Belieben aktiviert.
Private Variablen mit Closures
Wie Sie inzwischen wissen, können Variablen, die in einer Funktion erstellt wurden, außerhalb der Funktion nicht aufgerufen werden. Da sie nicht aufgerufen werden können, werden sie auch als private Variablen bezeichnet.
Manchmal müssen Sie jedoch auf eine solche private Variable zugreifen. Dies können Sie mithilfe von Closures tun.
function secret (secretCode) {
return {
saySecretCode () {
console.log(secretCode)
}
}
}
const theSecret = secret('CSS Tricks is amazing')
theSecret.saySecretCode()
// 'CSS Tricks is amazing'
saySecretCode ist in diesem Beispiel die einzige Funktion (eine Closure), die secretCode außerhalb der ursprünglichen geheimen Funktion verfügbar macht. Daher wird sie auch als **privilegierte Funktion** bezeichnet.
Scopes mit DevTools debuggen
Die DevTools von Chrome und Firefox machen es einfach, Variablen zu debuggen, auf die Sie im aktuellen Scope zugreifen können. Es gibt zwei Möglichkeiten, diese Funktionalität zu nutzen.
Die erste Möglichkeit besteht darin, das Schlüsselwort debugger in Ihren Code einzufügen. Dadurch wird die JavaScript-Ausführung in Browsern unterbrochen, sodass Sie debuggen können.
Hier ist ein Beispiel mit prepareCake.
function prepareCake (flavor) {
// Adding debugger
debugger
return function () {
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000)
}
}
const makeCakeLater = prepareCake('banana')
Wenn Sie Ihre DevTools öffnen und im Chrome zum Tab "Sources" (oder im Firefox zum Tab "Debugger") navigieren, sehen Sie die Ihnen zur Verfügung stehenden Variablen.

Sie können das Schlüsselwort debugger auch in die Closure verschieben. Beachten Sie, wie sich die Scope-Variablen diesmal ändern.
function prepareCake (flavor) {
return function () {
// Adding debugger
debugger
setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000)
}
}
const makeCakeLater = prepareCake('banana')

Die zweite Möglichkeit, diese Debugging-Funktionalität zu nutzen, besteht darin, direkt im Tab "Sources" (oder "Debugger") einen Haltepunkt in Ihren Code einzufügen, indem Sie auf die Zeilennummer klicken.

Zusammenfassung
Scopes und Closures sind nicht unglaublich schwer zu verstehen. Sie sind ziemlich einfach, sobald man weiß, wie man sie durch einen Einwegspiegel sieht.
Wenn Sie eine Variable in einer Funktion deklarieren, können Sie sie nur in der Funktion aufrufen. Diese Variablen werden als dem Funktions-Scope zugehörig bezeichnet.
Wenn Sie eine innere Funktion innerhalb einer anderen Funktion definieren, wird diese innere Funktion Closure genannt. Sie behält den Zugriff auf die in der äußeren Funktion erstellten Variablen.
Zögern Sie nicht, vorbeizukommen und Fragen zu stellen. Ich werde Ihnen so schnell wie möglich antworten.
Wenn Ihnen dieser Artikel gefallen hat, gefallen Ihnen vielleicht auch andere von mir geschriebene Artikel zu Front-End-Themen auf meinem Blog und in meinem Newsletter. Ich habe auch einen brandneuen (und kostenlosen!) E-Mail-Kurs: JavaScript Roadmap.
Schöne Ausarbeitung, danke. Ich denke jedoch, dass
constzur Definition von Konstanten und nicht von Variablen verwendet wird.Ich habe ein Dutzend Artikel über Closures gelesen, aber dies ist der erste, der Sinn ergibt! Vielen Dank! Die beste Lektüre seit langem!
Korrektur, der folgende Code
sollte sein
gutes Augenlicht
Wahr, und in allen Wiederholungen ;-)
Ansonsten ist die Idee des Einwegspiegels einfach und so treffend.
Das ist ein großartiger Beitrag!
Das ist die Art von Artikel auf css-tricks.com, die ich am meisten liebe: Gut strukturierte und erklärte Zusammenfassungen komplexer Themen. Artikel, zu denen man immer wieder zurückkehrt.
Sie würden nicht glauben, wie oft der Flexbox-Artikel mir schon geholfen hat. :D
Ich habe jedoch eine kleine Kritik: In Ihrem Artikel klingt es so, als ob Sie Variablen mit
constundletim globalen Scope vermeiden sollten – aber während Sie Globale im Allgemeinen vermeiden sollten (wie Sie schrieben), gibt es Fälle, in denen Sie sie benötigen – und in den meisten dieser Fälle sollten Sie definitiv Konstanten verwenden (wenn möglich) oder die Variablen mitletdeklarieren. Es gibt fast keine Fälle, in denen Sie Variable Shadowing wünschen würden – und die Erhaltung einer Ausnahme ist normalerweise dem stillen Fehlern im Code vorzuziehen.Sehr gute Ausarbeitung. Das Kuchenbackbeispiel hat mich ein wenig verwirrt. Können Sie ein reales Beispiel dafür geben, wann Sie Closures zur Steuerung von Seiteneffekten verwenden würden?
Ich bin auf Jacobs Seite. Das Kuchenbackbeispiel scheint ein Problem mit Seiteneffekten zu erzeugen, weil zwei Anliegen, Konfiguration und Ausführung, kombiniert werden. Sicherlich gibt es andere Wege, diese Schritte zu trennen, daher bin ich mir nicht sicher, warum ich Closures verwenden sollte, um dies zu tun.
Einwegspiegel ist die beste Analogie für Closures, die mir je begegnet ist. Danke!
Schöner Artikel. Nur Ihre Empfehlung, keine gehoisteten Funktionen zu verwenden, ist meiner Meinung nach nicht korrekt. Hoisting kann Ihre Dateien lesbarer und klarer machen, indem es Ihnen die Möglichkeit gibt, komplexe Funktionalitäten in Funktionen zu abstrahieren und diese am Ende der Datei zu platzieren, damit sie bei Bedarf inspiziert werden können.
Mit einem guten Editor wird es einfach, zu einer Deklaration zu klicken, und die Kernlogik am Anfang Ihrer Datei zu haben, wobei komplexe Abstraktionen nur einen Klick entfernt sind.
Ich denke, gesunder Menschenverstand und Code-Richtlinien sollten der Hauptentscheidungsmechanismus für solche Dinge sein.
Vielen Dank. Es ist wirklich ein guter Artikel über Scope und Closures.
Gute Erklärung. Aber das ist nur eine oberflächliche Übersicht über Closures und Scopes.
Wenn Sie wirklich wissen möchten, wie Closures tatsächlich funktionieren und wie sie auf Variablen und Argumente von übergeordneten Funktionen zugreifen, dann besuchen Sie diesen fantastischen Blog:
http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/
http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/
Ich weiß, dass der Inhalt auf diesem Blog sehr alt ist, aber er ist ein Schatz. Ich schlage vor, Sie lesen von Kapitel 1 an, da alle Konzepte miteinander verbunden sind.
Schöner Artikel, aber in Ihrem Beispiel für verschachtelte Scopes gibt es einen Fehler. Sie müssen innerFunction aufrufen, damit sie wie beabsichtigt ausgegeben wird.
Obwohl JavaScript es nicht stört, das Semikolon am Ende einer Codezeile auszulassen, ermutige ich die Leute wirklich dazu. Ich sehe darin keinen Nachteil und gleichzeitig haben Sie Klarheit über das Ende Ihrer Codezeile. Das Auslassen des Semikolons kann in JavaScript manchmal zu Chaos führen.
Toller Artikel
Ich mag auch das Einwegspiegel-Beispiel und erwähne es in meinem eigenen Closures-Post, der sich auf for-Schleifen konzentriert. Es dauert nur 4 Minuten zum Lesen.. schau es dir hier an
https://medium.com/@MoeHimed/closures-in-for-loops-in-layman-terms-1cf483e654bf