JavaScript Scope und Closures

Avatar of Zell Liew
Zell Liew am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

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.

Scopes in Funktionen verhalten sich wie ein Einwegspiegel. Sie können nach draußen sehen, aber Menschen draußen können Sie nicht sehen.

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

Mehrere Schichten von Funktionen bedeuten mehrere Schichten von Einwegspiegeln.

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:

  1. Zur Steuerung von Seiteneffekten
  2. 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.

Debugging des Scopes von prepareCake

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')
Debugging des Closure-Scopes

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.

Scopes mit Haltepunkten debuggen

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.