Grokking Scope in JavaScript

Der Geltungsbereich oder der Satz von Regeln, die bestimmen, wo Ihre Variablen leben, ist eines der grundlegendsten Konzepte einer Programmiersprache. Es ist in der Tat so grundlegend, dass man leicht vergessen kann, wie subtil die Regeln sein können!

Wenn Sie genau wissen, wie die JavaScript-Engine über den Umfang "denkt", werden Sie die üblichen Fehler, die beim Heben entstehen können, nicht aufschreiben, sich darauf vorbereiten, Ihren Kopf um Verschlüsse zu wickeln, und Sie so viel näher daran werden, niemals Fehler zu schreiben je nochmal.

… Nun, es hilft Ihnen trotzdem, das Heben und Schließen zu verstehen. 

In diesem Artikel werfen wir einen Blick auf:

  • die Grundlagen der Gültigkeitsbereiche in JavaScript
  • wie der Interpreter entscheidet, welche Variablen zu welchem ​​Bereich gehören
  • wie hochziehen Ja wirklich funktioniert
  • wie die ES6-Schlüsselwörter Lassen und const Wechsel das Spiel

Lass uns eintauchen.

Wenn Sie mehr über ES6 und die Verwendung von Syntax und Features zur Verbesserung und Vereinfachung Ihres JavaScript-Codes erfahren möchten, besuchen Sie die beiden folgenden Kurse:

Lexikalischer Umfang

Wenn Sie bereits eine JavaScript-Zeile geschrieben haben, wissen Sie das wo Du definieren Ihre Variablen bestimmen, wo Sie können benutzen Sie. Die Tatsache, dass die Sichtbarkeit einer Variablen von der Struktur Ihres Quellcodes abhängt, wird aufgerufen lexikalisch Umfang.

Es gibt drei Möglichkeiten, in JavaScript einen Bereich zu erstellen:

  1. Erstellen Sie eine Funktion. Innerhalb von Funktionen deklarierte Variablen sind nur in dieser Funktion sichtbar, auch in verschachtelten Funktionen.
  2. Variablen mit deklarieren Lassen oder const innerhalb eines Codeblocks. Solche Deklarationen sind nur innerhalb des Blocks sichtbar.
  3. Ein ... kreieren Fang Block. Glauben Sie es oder nicht, eigentlich tut Erstellen Sie einen neuen Bereich!
"streng verwenden"; var mr_global = "Herr Global"; function foo () var mrs_local = "Frau Lokal"; console.log ("Ich kann sehen" + mr_global + "und" + mrs_local + "."); function bar () console.log ("Ich kann auch sehen" + mr_global + "und" + mrs_local + ".");  foo (); // Funktioniert wie erwartet try console.log ("/ ich / sehe nicht" + mrs_local + ".");  catch (err) console.log ("Sie haben gerade ein" + err + "erhalten.));  let foo = "foo"; const bar = "bar"; console.log ("Ich kann" + foo + bar + "in seinem Block verwenden ...");  try console.log ("Aber nicht außerhalb.");  catch (err) console.log ("Du hast gerade ein anderes" + err + ".");  // wirft ReferenceError! console.log ("Beachten Sie, dass" + err + "außerhalb von 'catch' nicht existiert!") 

Das obige Snippet zeigt alle drei Umfangsmechanismen. Sie können es in Node oder Firefox ausführen, aber Chrome spielt nicht gut mit Lassen, noch.

Wir werden detailliert über jedes dieser Themen sprechen. Beginnen wir mit einem detaillierten Blick darauf, wie JavaScript ermittelt, welche Variablen zu welchem ​​Bereich gehören.

Der Zusammenstellungsprozess: Die Vogelperspektive

Wenn Sie ein Stück JavaScript ausführen, passieren zwei Dinge, damit es funktioniert.

  1. Zuerst wird Ihre Quelle kompiliert.
  2. Dann wird der kompilierte Code ausgeführt.

Während das Zusammenstellung Schritt, die JavaScript-Engine:

  1. nimmt alle Variablennamen zur Kenntnis
  2. registriert sie im entsprechenden Umfang
  3. reserviert Platz für ihre Werte

Es ist nur während Ausführung dass die JavaScript-Engine den Wert der Variablenreferenzen tatsächlich ihren Zuweisungswerten gleich setzt. Bis dahin sind sie nicht definiert

Schritt 1: Zusammenstellung

// Ich kann first_name überall in diesem Programm verwenden. Var first_name = "Peleke"; function popup (first_name) // Ich kann innerhalb dieser Funktion nur last_name verwenden. var last_name = "Sengstacke"; alert (erster_name + "+ letzter_name); popup (erster_name);

Lassen Sie uns durchgehen, was der Compiler macht.

Zuerst liest es die Zeile var first_name = "Peleke". Als nächstes bestimmt es was Umfang um die Variable zu speichern. Da wir uns auf der obersten Ebene des Skripts befinden, wird klar, dass wir im globaler Geltungsbereich. Dann speichert es die Variable Vorname auf den globalen Geltungsbereich und initialisiert seinen Wert auf nicht definiert.

Zweitens liest der Compiler die Zeile mit Funktions-Popup (Vorname). Weil der Funktion Das Schlüsselwort ist das erste in der Zeile, es erstellt einen neuen Gültigkeitsbereich für die Funktion, registriert die Definition der Funktion für den globalen Gültigkeitsbereich, und sucht nach Variablendeklarationen.

Sicher, der Compiler findet einen. Seit wir ... Haben var last_name = "Sengstacke" In der ersten Zeile unserer Funktion speichert der Compiler die Variable Nachname zum Geltungsbereich Pop-up-nicht auf den globalen Geltungsbereich und setzt seinen Wert auf nicht definiert

Da innerhalb der Funktion keine Variablendeklarationen mehr vorhanden sind, springt der Compiler zurück in den globalen Gültigkeitsbereich. Und da gibt es keine variablen Deklarationen mehr Dort, Diese Phase ist abgeschlossen.

Beachten Sie, dass wir das eigentlich nicht gemacht haben Lauf noch nichts. Der Compiler hat an dieser Stelle nur die Aufgabe, sicherzustellen, dass er den Namen aller kennt. es ist egal Was tun sie. 

Zu diesem Zeitpunkt weiß unser Programm:

  1. Es gibt eine Variable namens Vorname im globalen Bereich.
  2. Es gibt eine Funktion namens Pop-up im globalen Bereich.
  3. Es gibt eine Variable namens Nachname im Rahmen von Pop-up.
  4. Die Werte von beiden Vorname und Nachname sind nicht definiert.

Es ist egal, dass wir diese Variablen an anderer Stelle in unserem Code zugewiesen haben. Dafür sorgt der Motor in Ausführung.

Schritt 2: Ausführung

Im nächsten Schritt liest die Engine unseren Code erneut, diesmal jedoch, führt aus es. 

Zuerst liest es die Zeile, var first_name = "Peleke". Dazu sucht die Engine die aufgerufene Variable Vorname. Da der Compiler bereits eine Variable mit diesem Namen registriert hat, findet die Engine diese und setzt ihren Wert auf "Peleke".

Als nächstes liest es die Zeile, Funktions-Popup (Vorname). Da sind wir nicht ausführen Die Funktion hier interessiert die Engine nicht und überspringt sie.

Schließlich liest es die Zeile popup (vorname). Seit wir sind Hier eine Funktion ausführen, die Engine:

  1. sucht den Wert von Pop-up
  2. sucht den Wert von Vorname
  3. führt aus Pop-up als Funktion, den Wert von übergeben Vorname als Parameter

Wenn es ausgeführt wird Pop-up, es durchläuft den gleichen Prozess, aber diesmal in der Funktion Pop-up. Es:

  1. sucht die Variable namens Nachname
  2. setzt NachnameWert gleich "Sengstacke"
  3. schaut nach oben warnen, Ausführen als Funktion mit "Peleke Sengstacke" als dessen Parameter

Es stellt sich heraus, dass unter der Motorhaube viel mehr los ist, als wir gedacht hätten!

Nachdem Sie nun verstanden haben, wie JavaScript den Code liest und ausführt, den Sie schreiben, sind wir bereit, etwas näher bei sich zu Hause anzugehen: wie das Heben funktioniert.

Heben unter dem Mikroskop

Beginnen wir mit etwas Code.

Bar(); Funktionsleiste () if (! foo) alert (foo + "? Dies ist seltsam ...");  var foo = "bar";  gebrochen (); // TypeError! var broken = function () alert ("Dieser Alarm wird nicht angezeigt!"); 

Wenn Sie diesen Code ausführen, werden Sie drei Dinge bemerken:

  1. Sie können beziehen auf foo bevor Sie es zuweisen, aber sein Wert ist nicht definiert.
  2. Sie können Anruf gebrochen bevor du es definierst, bekommst du aber eine TypeError.
  3. Sie können Anruf Bar bevor Sie es definieren, und es funktioniert wie gewünscht.

Heben verweist auf die Tatsache, dass JavaScript alle unsere deklarierten Variablennamen verfügbar macht überall in ihren Bereichen - einschließlich Vor wir ordnen ihnen zu.

Die drei Fälle in diesem Snippet sind die drei Fälle, die Sie in Ihrem eigenen Code beachten müssen. Wir gehen jeden Schritt einzeln durch.

Variablendeklarationen anheben

Denken Sie daran, wenn der JavaScript-Compiler eine Zeile wie liest var foo = "bar", es:

  1. registriert den Namen foo bis zum nächsten Bereich
  2. setzt den Wert von foo zu undefiniert

Den Grund können wir verwenden foo Bevor wir es zuweisen, ist es, weil, wenn die Engine die Variable mit diesem Namen nachschlägt, es tut existieren. Deshalb wirft es keine ReferenceError

Stattdessen erhält es den Wert nicht definiert, und versucht, das zu verwenden, was Sie danach gefragt haben. Normalerweise ist das ein Fehler.

Wenn wir dies berücksichtigen, können wir uns vorstellen, dass JavaScript in unserer Funktion erkannt wird Bar ist eher so:

Funktionsleiste () var foo; // undefined if (! foo) //! undefined ist wahr, also Alarm (foo + "? Dies ist seltsam ...");  foo = "bar"; 

Dies ist das Erste Regel beim Hochziehen, wenn du willst: Variablen stehen zur Verfügung in ihrem gesamten Umfang, aber den Wert haben nicht definiert bis Ihr Code ihnen zuweist.

Ein allgemeines JavaScript-Idiom ist, alle Ihre zu schreiben var Erklärungen oben in ihrem Geltungsbereich, anstatt dort, wo Sie sie zuerst verwenden. Um Doug Crockford zu beschreiben, hilft dies Ihrem Code lesen eher wie es läuft.

Wenn Sie darüber nachdenken, macht das Sinn. Es ist ziemlich klar warum Bar verhält sich so, wie wir es tun, wenn wir unseren Code so schreiben, wie JavaScript ihn liest, oder? Warum also nicht einfach so schreiben alles die Zeit?  

Heben von Funktionsausdrücken

Die Tatsache, dass wir eine bekommen haben TypeError als wir versuchten, es auszuführen gebrochen Bevor wir definiert haben, handelt es sich nur um einen Sonderfall der ersten Regel des Anhebens.

Wir haben eine Variable definiert, genannt gebrochen, die der Compiler im globalen Gültigkeitsbereich registriert und gleich setzt nicht definiert. Wenn wir versuchen, es auszuführen, sucht der Motor nach dem Wert von gebrochen, findet, dass es ist nicht definiert, und versucht es auszuführen nicht definiert als eine Funktion.

Offensichtlich, nicht definiert ist nicht eine Funktion, deshalb bekommen wir eine TypeError!

Deklarationen für Hebefunktionen

Schließlich erinnern wir uns daran, dass wir anrufen konnten Bar bevor wir es definiert haben. Dies liegt an der Zweite Regel des Anhebens: Wenn der JavaScript-Compiler eine Funktionsdeklaration findet, erhält er beide Namen und Definition oben im Geltungsbereich verfügbar. Nochmals unseren Code umschreiben:

Funktionsleiste () if (! foo) alert (foo + "? Dies ist seltsam ...");  var foo = "bar";  var gebrochen; // undefined bar (); // bar ist bereits definiert, führt fine broken () aus; // Kann undefined nicht ausführen! broken = function () alert ("Dieser Alarm wird nicht angezeigt!"); 

 Wieder macht es viel mehr Sinn, wenn Sie schreiben als JavaScript liest, denkst du nicht?

Zu überprüfen:

  1. Die Namen sowohl der Variablendeklarationen als auch der Funktionsausdrücke sind in ihrem gesamten Bereich verfügbar Werte sind nicht definiert bis zur Abtretung.
  2. Die Namen und Definitionen von Funktionsdeklarationen sind im gesamten Geltungsbereich verfügbar, noch vor ihre Definitionen.

Schauen wir uns jetzt zwei neue Tools an, die etwas anders funktionieren: Lassen und const.

Lassenconst, & die temporale tote Zone

nicht wie var Deklarationen, mit deklarierte Variablen Lassen und const nicht vom Compiler gehisst werden.

Zumindest nicht genau. 

Erinnern Sie sich, wie wir anrufen konnten gebrochen, bekam aber eine TypeError weil wir versucht haben, auszuführen nicht definiert? Wenn wir definiert hätten gebrochen mit Lassen, wir hätten bekommen ReferenceError, stattdessen:

"streng verwenden"; // Sie müssen "strict" verwenden, um dies in Node zu versuchen broken (); // Referenzfehler! let broken = function () alert ("Dieser Alarm wird nicht angezeigt!"); 

Wenn der JavaScript-Compiler im ersten Durchlauf Variablen in seinem Gültigkeitsbereich registriert, wird er behandelt Lassen und const anders als es tut var

Wenn es einen findet var Deklaration registrieren wir den Namen der Variablen in ihrem Gültigkeitsbereich und initialisieren ihren Wert sofort mit nicht definiert.

Mit Lassen, jedoch der Compiler tut Registrieren Sie die Variable in ihrem Gültigkeitsbereich, aber nichtseinen Wert auf initialisieren nicht definiert. Stattdessen wird die Variable nicht initialisiert, bis um Die Engine führt Ihre Zuweisungsanweisung aus. Durch den Zugriff auf den Wert einer nicht initialisierten Variablen wird a ReferenceError, Das erklärt, warum das obige Snippet wirft, wenn wir es ausführen.

Der Abstand zwischen dem Anfang von oben im Gültigkeitsbereich von a Lassen Deklaration und die Zuweisungsanweisung heißt Temporale tote Zone. Der Name rührt von der Tatsache her, obwohl der Motor weiß über eine Variable namens foo am oberen Rand des Bereichs von Bar, Die Variable ist "tot", weil sie keinen Wert hat.

… Auch weil es Ihr Programm tötet, wenn Sie versuchen, es frühzeitig zu verwenden.

Das const Das Schlüsselwort funktioniert genauso wie Lassen, mit zwei wesentlichen unterschieden:

  1. Sie Muss Vergeben Sie einen Wert, wenn Sie mit deklarieren const.
  2. Sie kann nicht Werte einer mit deklarierten Variablen neu zuweisen const.

Das garantiert das const werden immerhaben den Wert, den Sie ursprünglich zugewiesen haben.

// Das ist legal const React = required ('reagieren'); // Das ist absolut nicht legal const crypto; crypto = required ('crypto');

Bereich blockieren

Lassen und const unterscheiden sich von var auf eine andere Weise: die Größe ihrer Bereiche.

Wenn Sie eine Variable mit deklarieren var, es ist sichtbar möglichst weit oben in der Gültigkeitsbereichskette - normalerweise oben in der nächstgelegenen Funktionsdeklaration oder im globalen Gültigkeitsbereich, wenn Sie sie auf oberster Ebene deklarieren. 

Wenn Sie eine Variable mit deklarieren Lassen oder const, es ist jedoch sichtbar als örtlich wie möglich-nur innerhalb des nächsten Blocks.

EIN Block ist ein Codeabschnitt, der durch geschweifte Klammern hervorgehoben wird ob/sonst Blöcke, zum Schleifen und explizit "blockiert" Codeabschnitte, wie in diesem Snippet.

"streng verwenden"; let foo = "foo"; if (foo) const bar = "bar"; var foobar = foo + bar; console.log ("Ich kann" + Balken + "in diesem Block sehen.");  try console.log ("Ich kann" + foo + "in diesem Block sehen, aber nicht" + bar + "."););  catch (err) console.log ("Du hast ein" + err + ".");  try console.log (foo + bar); // wird wegen 'foo' ausgelöst, aber beide sind undefiniert catch (err) console.log ("Sie haben gerade ein" + err + ".");  console.log (foobar); // Funktioniert gut

Wenn Sie eine Variable mit deklarieren const oder Lassen In einem Block ist es nur sichtbar innerhalb des Blocks und nur nachdem Sie es zugewiesen haben.

Eine mit deklarierte Variable var, ist jedoch sichtbar so weit wie möglich-in diesem Fall im globalen Bereich.

Wenn Sie sich für die winzigsten Details von interessieren Lassen und const, Lesen Sie in Exploring ES6: Variables and Scoping nach, was Dr. Rauschmayer dazu zu sagen hat, und werfen Sie einen Blick auf die MDN-Dokumentation.  

Lexikalisch diese & Pfeilfunktionen

An der Oberfläche, diese scheint nicht viel mit Umfang zu tun zu haben. Und tatsächlich tut es JavaScript nicht löse die Bedeutung von diese gemäß den Regeln des Geltungsbereichs, über die wir hier gesprochen haben.

Zumindest normalerweise nicht. JavaScript tut es notorisch nicht Löse die Bedeutung der diese Keyword basierend auf dem Ort, an dem Sie es verwendet haben:

var foo = name: 'foo', sprachen: ['spanisch', 'französisch', 'italienisch'], sprich: function speak () this.languages.forEach (function (sprache) console.log (this.) Name + "spricht" + Sprache + ".");); foo.speak ();

Die meisten von uns würden es erwarten diese meinen foo in der für jeden Schleife, weil es das war, was es direkt außerhalb bedeutete. Mit anderen Worten, wir erwarten, dass JavaScript die Bedeutung von löst diese lexikalisch.

Aber es tut nicht.

Stattdessen wird ein erstellt Neu diese in jeder Funktion, die Sie definieren, und entscheidet anhand dessen, was sie bedeutet Wie Sie rufen die Funktion nicht auf woher du hast es definiert.

Dieser erste Punkt ähnelt dem Fall der Neudefinition irgendein Variable in einem untergeordneten Bereich:

Funktion foo () var bar = "bar"; function baz () // Die Wiederverwendung von Variablennamen wie dieser wird "Shadowing" genannt. var bar = "BAR"; console.log (Bar); // BAR baz ();  foo (); // BAR

Ersetzen Bar mit diese, und das Ganze sollte sich sofort aufklären!

Traditionell bekommen diese Um zu funktionieren, wie wir erwarten, dass einfache lexikalisch definierte Variablen funktionieren, ist eine von zwei Problemumgehungen erforderlich:

var foo = name: 'foo', sprachen: ['spanisch', 'französisch', 'italienisch'], speak_self: function speak_s () var self = this; self.languages.forEach (function (language) console.log (self.name + "spricht +" + ".");), speak_bound: function speak_b () this.languages.forEach (function (language ) console.log (this.name + "spricht" + language + "."); .bind (foo)); // Häufiger: .bind (this); ;

Im selbst sprechen, wir retten die Bedeutung von diese auf die Variable selbst, und verwenden Das Variable, um die gewünschte Referenz zu erhalten. Im speak_bound, wir gebrauchen binden zu permanent Punkt diese zu einem bestimmten Objekt.

ES2015 bietet uns eine neue Alternative: Pfeilfunktionen.

Im Gegensatz zu "normalen" Funktionen haben Pfeilfunktionen dies nicht Schatten ihres übergeordneten Bereichs diese Wert durch eigene Einstellung. Sie lösen vielmehr seine Bedeutung auf lexikalisch. 

Mit anderen Worten, wenn Sie verwenden diese In einer Pfeilfunktion sucht JavaScript seinen Wert wie jede andere Variable.

Zunächst wird der lokale Gültigkeitsbereich für a geprüft diese Wert. Da die Pfeilfunktionen keinen Wert einstellen, wird kein gefunden. Als nächstes prüft es die Elternteil Spielraum für ein diese Wert. Wenn es einen findet, wird es stattdessen verwendet.

Dadurch können wir den Code wie folgt umschreiben:

var foo = name: 'foo', sprachen: ['spanisch', 'französisch', 'italienisch'], sprich: function speak () this.languages.forEach ((language) => console.log (this .name + "spricht" + sprache + "."););   

Wenn Sie mehr über die Pfeilfunktionen erfahren möchten, werfen Sie einen Blick auf den hervorragenden Kurs von Envato Tuts + Instructor Dan Wellman zu JavaScript ES6 Fundamentals sowie die MDN-Dokumentation zu den Pfeilfunktionen.

Fazit

Wir haben bis jetzt eine Menge Boden zurückgelegt! In diesem Artikel haben Sie Folgendes gelernt:

  • Variablen werden während ihres Zeitraums in ihrem Gültigkeitsbereich registriert Zusammenstellung, und ihren Zuweisungswerten während zugeordnet Ausführung.
  • Verweis auf mit deklarierte VariablenLassen oder const vor dem Einsatz wirft ein ReferenceError, und dass solche Variablen auf den nächsten Block beschränkt sind.
  • PfeilfunktionenErlaube uns, eine lexikalische Bindung von zu erreichen diese, und umgehen Sie die traditionelle dynamische Bindung.

Sie haben auch die zwei Regeln des Hebens gesehen:

  • Das Erste Regel beim Hochziehen: Diese Funktionsausdrücke und var Deklarationen sind in allen definierten Bereichen verfügbar, haben jedoch den Wert nicht definiert bis Ihre Zuweisungsanweisungen ausgeführt werden.
  • Das Zweite Regel des Anhebens: Dass die Namen der Funktionsdeklarationen und Ihre Körper sind in allen Bereichen verfügbar, in denen sie definiert sind.

Ein guter nächster Schritt ist es, Ihr Wissen über JavaScripts Geltungsbereiche zu nutzen, um Ihren Kopf um Verschlüsse zu wickeln. Informieren Sie sich dazu über Kyle Simpsons Scopes & Closures.

Schließlich gibt es noch viel mehr zu sagen diese als ich hier abdecken konnte. Wenn das Schlüsselwort immer noch nach so viel schwarzer Magie aussieht, werfen Sie einen Blick auf diese & Objekt-Prototypen, um den Kopf zu bekommen.

Nehmen Sie in der Zwischenzeit das, was Sie gelernt haben, und schreiben Sie weniger Fehler!

Lernen Sie JavaScript: Das komplette Handbuch

Wir haben ein umfassendes Handbuch zusammengestellt, mit dessen Hilfe Sie JavaScript erlernen können, egal ob Sie gerade erst als Webentwickler anfangen oder sich mit fortgeschrittenen Themen befassen möchten.