Was ist der Umfang von Variablen in Javascript? Haben sie innerhalb und außerhalb einer Funktion den gleichen Umfang? Oder spielt es überhaupt eine Rolle? Wo werden die Variablen gespeichert, wenn sie global definiert sind?
Was ist der Umfang von Variablen in Javascript? Haben sie innerhalb und außerhalb einer Funktion den gleichen Umfang? Oder spielt es überhaupt eine Rolle? Wo werden die Variablen gespeichert, wenn sie global definiert sind?
Antworten:
JavaScript hat lexikalische (auch statische) Gültigkeitsbereiche und Schließungen. Dies bedeutet, dass Sie den Umfang eines Bezeichners anhand des Quellcodes erkennen können.
Die vier Bereiche sind:
Außerhalb der Sonderfälle des globalen Bereichs und des Modulbereichs werden Variablen mit var
(Funktionsbereich), let
(Blockbereich) und const
(Blockbereich) deklariert . Die meisten anderen Formen der Bezeichnerdeklaration haben einen Blockbereich im strengen Modus.
Der Bereich ist der Bereich der Codebasis, über den ein Bezeichner gültig ist.
Eine lexikalische Umgebung ist eine Zuordnung zwischen Bezeichnernamen und den damit verbundenen Werten.
Der Bereich besteht aus einer verknüpften Verschachtelung lexikalischer Umgebungen, wobei jede Ebene in der Verschachtelung einer lexikalischen Umgebung eines Ahnenausführungskontexts entspricht.
Diese verknüpften lexikalischen Umgebungen bilden eine Bereichskette. Bei der Bezeichnerauflösung wird entlang dieser Kette nach einem passenden Bezeichner gesucht.
Die Auflösung der Kennung erfolgt nur in eine Richtung: nach außen. Auf diese Weise können äußere lexikalische Umgebungen nicht in innere lexikalische Umgebungen "sehen".
Es gibt drei relevante Faktoren bei der Entscheidung über den Umfang eines Bezeichners in JavaScript:
Einige der Möglichkeiten, wie Bezeichner deklariert werden können:
var
, let
undconst
var
im nicht strengen Modus fehlen )import
Aussageneval
Einige der Standortkennungen können deklariert werden:
Mit deklarierte Bezeichner var
haben einen Funktionsumfang , außer wenn sie direkt im globalen Kontext deklariert werden. In diesem Fall werden sie als Eigenschaften für das globale Objekt hinzugefügt und haben einen globalen Gültigkeitsbereich. Es gibt separate Regeln für ihre Verwendung in eval
Funktionen.
Bezeichner, die mit deklariert wurden let
und const
einen Blockbereich haben , außer wenn sie direkt im globalen Kontext deklariert werden. In diesem Fall haben sie einen globalen Bereich.
Hinweis: let
, const
und var
sind alle hochgezogen . Dies bedeutet, dass ihre logische Definitionsposition die Spitze ihres umschließenden Bereichs (Block oder Funktion) ist. Variablen, die als verwendet deklariert wurden let
und const
erst gelesen oder zugewiesen werden können, wenn die Steuerung den Deklarationspunkt im Quellcode überschritten hat. Die Zwischenzeit wird als zeitliche Totzone bezeichnet.
function f() {
function g() {
console.log(x)
}
let x = 1
g()
}
f() // 1 because x is hoisted even though declared with `let`!
Funktionsparameternamen sind auf den Funktionskörper beschränkt. Beachten Sie, dass dies eine leichte Komplexität aufweist. Als Standardargumente deklarierte Funktionen schließen über der Parameterliste und nicht über dem Hauptteil der Funktion.
Funktionsdeklarationen haben einen Blockbereich im strengen Modus und einen Funktionsbereich im nicht strengen Modus. Hinweis: Der nicht strenge Modus ist ein komplizierter Satz neuer Regeln, die auf den skurrilen historischen Implementierungen verschiedener Browser basieren.
Benannte Funktionsausdrücke sind auf sich selbst beschränkt (z. B. zum Zweck der Rekursion).
Im nicht strengen Modus haben implizit definierte Eigenschaften für das globale Objekt einen globalen Bereich, da sich das globale Objekt am oberen Rand der Bereichskette befindet. Im strengen Modus sind diese nicht zulässig.
In eval
Zeichenfolgen werden mit deklarierte Variablen var
im aktuellen Bereich platziert oder, wenn eval
sie indirekt verwendet werden, als Eigenschaften für das globale Objekt.
Im Folgenden wird ein Reference weil die Namen werfen x
, y
und z
haben keine Bedeutung außerhalb der Funktion f
.
function f() {
var x = 1
let y = 1
const z = 1
}
console.log(typeof x) // undefined (because var has function scope!)
console.log(typeof y) // undefined (because the body of the function is a block)
console.log(typeof z) // undefined (because the body of the function is a block)
Im Folgenden wird ein ReferenceError für y
und ausgelöst z
, jedoch nicht für x
, da die Sichtbarkeit von x
nicht durch den Block eingeschränkt wird. Blöcke, die die Körper von Kontrollstrukturen definieren möchten if
, for
und while
, ähnlich verhalten.
{
var x = 1
let y = 1
const z = 1
}
console.log(x) // 1
console.log(typeof y) // undefined because `y` has block scope
console.log(typeof z) // undefined because `z` has block scope
Im Folgenden x
ist außerhalb der Schleife sichtbar, da var
Funktionsumfang hat:
for(var x = 0; x < 5; ++x) {}
console.log(x) // 5 (note this is outside the loop!)
... wegen dieses Verhaltens müssen Sie vorsichtig sein, wenn Sie Variablen schließen, die mit var
in Schleifen deklariert wurden . Hier ist nur eine Instanz einer Variablen x
deklariert, die sich logisch außerhalb der Schleife befindet.
Die folgenden Drucke 5
werden fünfmal und dann 5
zum sechsten Mal console.log
außerhalb der Schleife gedruckt :
for(var x = 0; x < 5; ++x) {
setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop
}
console.log(x) // note: visible outside the loop
Das Folgende wird gedruckt, undefined
weil x
es einen Blockbereich hat. Die Rückrufe werden einzeln asynchron ausgeführt. Neues Verhalten für let
Variablen bedeutet, dass jede anonyme Funktion über einer anderen Variablen mit dem Namen geschlossen wird x
(anders als dies der Fall gewesen wäre var
) und daher Ganzzahlen 0
durchgedruckt 4
werden:
for(let x = 0; x < 5; ++x) {
setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables
}
console.log(typeof x) // undefined
Im Folgenden wird KEIN a ausgelöst, ReferenceError
da die Sichtbarkeit von x
nicht durch den Block eingeschränkt wird. Es wird jedoch gedruckt, undefined
da die Variable nicht initialisiert wurde (aufgrund der if
Anweisung).
if(false) {
var x = 1
}
console.log(x) // here, `x` has been declared, but not initialised
Eine Variable, die am Anfang einer for
Schleife mit let
deklariert wird, ist auf den Hauptteil der Schleife beschränkt:
for(let x = 0; x < 10; ++x) {}
console.log(typeof x) // undefined, because `x` is block-scoped
Im Folgenden wird a ausgelöst, ReferenceError
da die Sichtbarkeit von x
durch den Block eingeschränkt wird:
if(false) {
let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped
Variablen, die mit oder für Module deklariert wurden var
, sind alle auf Module beschränkt:let
const
// module1.js
var x = 0
export function f() {}
//module2.js
import f from 'module1.js'
console.log(x) // throws ReferenceError
Im Folgenden wird eine Eigenschaft für das globale Objekt deklariert, da var
im globalen Kontext deklarierte Variablen als Eigenschaften zum globalen Objekt hinzugefügt werden:
var x = 1
console.log(window.hasOwnProperty('x')) // true
let
und const
fügen Sie im globalen Kontext keine Eigenschaften zum globalen Objekt hinzu, haben aber dennoch einen globalen Bereich:
let x = 1
console.log(window.hasOwnProperty('x')) // false
Funktionsparameter können als im Funktionskörper deklariert betrachtet werden:
function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function
Catch-Block-Parameter sind auf den Catch-Block-Body beschränkt:
try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block
Benannte Funktionsausdrücke beziehen sich nur auf den Ausdruck selbst:
(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression
Im nicht strengen Modus werden implizit definierte Eigenschaften für das globale Objekt global definiert. Im strengen Modus wird eine Fehlermeldung angezeigt.
x = 1 // implicitly defined property on the global object (no "var"!)
console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true
Im nicht strengen Modus haben Funktionsdeklarationen einen Funktionsumfang. Im strengen Modus haben sie Blockbereich.
'use strict'
{
function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped
Der Bereich ist definiert als der lexikalische Codebereich, über den ein Bezeichner gültig ist.
In JavaScript hat jedes Funktionsobjekt eine versteckte [[Environment]]
Referenz, die auf die lexikalische Umgebung des Ausführungskontexts (Stapelrahmen) verweist, in dem es erstellt wurde.
Wenn Sie eine Funktion aufrufen, wird die versteckte [[Call]]
Methode aufgerufen. Diese Methode erstellt einen neuen Ausführungskontext und stellt eine Verbindung zwischen dem neuen Ausführungskontext und der lexikalischen Umgebung des Funktionsobjekts her. Dazu wird der [[Environment]]
Wert für das Funktionsobjekt in ein äußeres Referenzfeld in der lexikalischen Umgebung des neuen Ausführungskontexts kopiert .
Beachten Sie, dass diese Verknüpfung zwischen dem neuen Ausführungskontext und der lexikalischen Umgebung des Funktionsobjekts als Abschluss bezeichnet wird .
Daher wird in JavaScript der Gültigkeitsbereich über lexikalische Umgebungen implementiert, die durch äußere Referenzen in einer "Kette" miteinander verbunden sind. Diese Kette von lexikalischen Umgebungen wird als Bereichskette bezeichnet, und die Auflösung von Bezeichnern erfolgt durch Durchsuchen der Kette nach einem passenden Bezeichner.
Erfahren Sie mehr .
Javascript verwendet Bereichsketten, um den Bereich für eine bestimmte Funktion festzulegen. In der Regel gibt es einen globalen Bereich, und jede definierte Funktion verfügt über einen eigenen verschachtelten Bereich. Jede in einer anderen Funktion definierte Funktion hat einen lokalen Bereich, der mit der äußeren Funktion verknüpft ist. Es ist immer die Position in der Quelle, die den Bereich definiert.
Ein Element in der Bereichskette ist im Grunde eine Karte mit einem Zeiger auf den übergeordneten Bereich.
Beim Auflösen einer Variablen beginnt Javascript im innersten Bereich und sucht nach außen.
Global deklarierte Variablen haben einen globalen Gültigkeitsbereich. Innerhalb einer Funktion deklarierte Variablen haben einen Gültigkeitsbereich für diese Funktion und schattieren globale Variablen mit demselben Namen.
(Ich bin sicher, dass es viele Feinheiten gibt, auf die echte JavaScript-Programmierer in anderen Antworten hinweisen können. Insbesondere bin ich auf diese Seite gestoßen, was genau genau this
zu jeder Zeit bedeutet. Hoffentlich reicht dieser einführende Link aus, um Ihnen den Einstieg zu erleichtern .)
Traditionell hat JavaScript nur zwei Arten von Gültigkeitsbereichen:
Ich werde darauf nicht näher eingehen, da es bereits viele andere Antworten gibt, die den Unterschied erklären.
Die neuesten JavaScript-Spezifikationen ermöglichen jetzt auch einen dritten Bereich:
Traditionell erstellen Sie Ihre Variablen folgendermaßen:
var myVariable = "Some text";
Blockbereichsvariablen werden wie folgt erstellt:
let myVariable = "Some text";
Beachten Sie den folgenden Code, um den Unterschied zwischen Funktionsumfang und Blockumfang zu verstehen:
// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here
function loop(arr) {
// i IS known here, but undefined
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
for( var i = 0; i < arr.length; i++ ) {
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
};
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
for( let j = 0; j < arr.length; j++ ) {
// i IS known here, and has a value
// j IS known here, and has a value
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
};
// i IS known here, and has a value
// j IS NOT known here
// k IS known here, but has a value only the second time loop is called
// l IS NOT known here
}
loop([1,2,3,4]);
for( var k = 0; k < arr.length; k++ ) {
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here
};
for( let l = 0; l < arr.length; l++ ) {
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS known here, and has a value
};
loop([1,2,3,4]);
// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here
Hier können wir sehen, dass unsere Variable j
nur in der ersten for-Schleife bekannt ist, nicht jedoch vorher und nachher. Unsere Variable i
ist jedoch in der gesamten Funktion bekannt.
Beachten Sie auch, dass Variablen mit Blockbereich nicht bekannt sind, bevor sie deklariert werden, da sie nicht angehoben werden. Sie dürfen auch nicht dieselbe Variable mit Blockbereich innerhalb desselben Blocks neu deklarieren. Dies macht Variablen mit Blockbereich weniger fehleranfällig als Variablen mit globalem oder funktionalem Bereich, die angehoben werden und bei mehreren Deklarationen keine Fehler verursachen.
Ob die Verwendung heute sicher ist oder nicht, hängt von Ihrer Umgebung ab:
Wenn Sie serverseitigen JavaScript-Code ( Node.js ) schreiben , können Sie die let
Anweisung sicher verwenden .
Wenn Sie clientseitigen JavaScript-Code schreiben und einen browserbasierten Transpiler (wie Traceur oder babel-standalone ) verwenden, können Sie die let
Anweisung sicher verwenden. Ihr Code ist jedoch in Bezug auf die Leistung wahrscheinlich alles andere als optimal.
Wenn Sie clientseitigen JavaScript-Code schreiben und einen knotenbasierten Transpiler verwenden (wie das Traceur-Shell-Skript oder Babel ), können Sie die let
Anweisung sicher verwenden . Und da Ihr Browser nur über den transpilierten Code Bescheid weiß, sollten die Leistungsnachteile begrenzt sein.
Wenn Sie clientseitigen JavaScript-Code schreiben und keinen Transpiler verwenden, müssen Sie die Browserunterstützung in Betracht ziehen.
Dies sind einige Browser, die überhaupt nicht unterstützen let
:
Auf dieser Seite finden Sie eine aktuelle Übersicht darüber, welche Browser die let
Aussage zum Zeitpunkt des Lesens dieser Antwort unterstützen .Can I Use
(*) Global und funktional scoped Variablen initialisiert und verwendet werden , bevor sie deklariert werden , da JavaScript - Variablen werden gehisst . Dies bedeutet, dass Deklarationen immer ganz oben im Geltungsbereich stehen.
Hier ist ein Beispiel:
<script>
var globalVariable = 7; //==window.globalVariable
function aGlobal( param ) { //==window.aGlobal();
//param is only accessible in this function
var scopedToFunction = {
//can't be accessed outside of this function
nested : 3 //accessible by: scopedToFunction.nested
};
anotherGlobal = {
//global because there's no `var`
};
}
</script>
Sie sollten die Schließungen untersuchen und untersuchen, wie Sie sie verwenden können, um private Mitglieder zu gewinnen .
Der Schlüssel, so wie ich es verstehe, ist, dass Javascript ein Scoping auf Funktionsebene im Vergleich zum häufigeren C-Block-Scoping hat.
In "Javascript 1.7" (Mozillas Erweiterung zu Javascript) kann man auch Block-Scope-Variablen mit der folgenden let
Anweisung deklarieren :
var a = 4;
let (a = 3) {
alert(a); // 3
}
alert(a); // 4
let
.
Die Idee, in JavaScript zu scoping, als es ursprünglich von Brendan Eich entworfen wurde, kam von der HyperCard- Skriptsprache HyperTalk .
In dieser Sprache wurden die Anzeigen ähnlich wie bei einem Stapel Karteikarten erstellt. Es gab eine Hauptkarte, die als Hintergrund bezeichnet wurde. Es war transparent und kann als unterste Karte angesehen werden. Alle Inhalte auf dieser Basiskarte wurden mit darauf platzierten Karten geteilt. Jede oben platzierte Karte hatte ihren eigenen Inhalt, der Vorrang vor der vorherigen Karte hatte, aber auf Wunsch immer noch Zugriff auf die vorherigen Karten hatte.
Genau so ist das JavaScript-Scoping-System aufgebaut. Es hat nur verschiedene Namen. Die Karten in JavaScript werden als Ausführungskontexte ECMA bezeichnet . Jeder dieser Kontexte enthält drei Hauptteile. Eine variable Umgebung, eine lexikalische Umgebung und diese Bindung. Zurück zur Kartenreferenz: Die lexikalische Umgebung enthält den gesamten Inhalt früherer Karten, die sich weiter unten im Stapel befinden. Der aktuelle Kontext befindet sich oben im Stapel, und alle dort deklarierten Inhalte werden in der variablen Umgebung gespeichert. Die variable Umgebung hat bei Namenskollisionen Vorrang.
Diese Bindung zeigt auf das enthaltende Objekt. Manchmal ändern sich Bereiche oder Ausführungskontexte, ohne dass sich das enthaltende Objekt ändert, z. B. in einer deklarierten Funktion, in der sich das enthaltende Objekt befinden kann, window
oder in einer Konstruktorfunktion.
Diese Ausführungskontexte werden jedes Mal erstellt, wenn die Steuerung übertragen wird. Die Steuerung wird übertragen, wenn die Ausführung des Codes beginnt, und dies erfolgt hauptsächlich über die Funktionsausführung.
Das ist also die technische Erklärung. In der Praxis ist es wichtig, sich dies in JavaScript zu merken
Wenn Sie dies auf eines der vorherigen Beispiele (5. "Schließen") auf dieser Seite anwenden, können Sie dem Stapel von Ausführungskontexten folgen. In diesem Beispiel befinden sich drei Kontexte im Stapel. Sie werden durch den äußeren Kontext, den Kontext in der sofort aufgerufenen Funktion, die von var six aufgerufen wird, und den Kontext in der zurückgegebenen Funktion innerhalb der sofort aufgerufenen Funktion von var six definiert.
i ) Der äußere Kontext. Es hat eine variable Umgebung von a = 1
ii ) Der IIFE-Kontext hat eine lexikalische Umgebung von a = 1, aber eine variable Umgebung von a = 6, die im Stapel Vorrang hat
iii ) Der zurückgegebene Funktionskontext hat eine lexikalische Umgebung Umgebung von a = 6 und das ist der Wert, auf den in der Warnung verwiesen wird, wenn sie aufgerufen wird.
1) Es gibt einen globalen Bereich, einen Funktionsbereich sowie die Bereiche with und catch. Es gibt im Allgemeinen keinen Bereich auf Blockebene für Variablen - die Anweisungen with und catch fügen ihren Blöcken Namen hinzu.
2) Bereiche werden von Funktionen bis zum globalen Bereich verschachtelt.
3) Die Eigenschaften werden durch Durchlaufen der Prototypenkette aufgelöst. Die with-Anweisung bringt Objekteigenschaftsnamen in den durch den with-Block definierten lexikalischen Bereich.
BEARBEITEN: ECMAAScript 6 (Harmony) unterstützt let, und ich weiß, dass Chrome ein 'Harmony'-Flag zulässt, also unterstützt es es vielleicht.
Es wäre eine Unterstützung für das Scoping auf Blockebene, aber Sie müssen das Schlüsselwort verwenden, um dies zu erreichen.
BEARBEITEN: Basierend auf Benjamins Hinweis auf die with- und catch-Anweisungen in den Kommentaren habe ich den Beitrag bearbeitet und weitere hinzugefügt. Sowohl die with- als auch die catch-Anweisung führen Variablen in ihre jeweiligen Blöcke ein, und das ist ein Blockbereich. Diese Variablen sind auf die Eigenschaften der an sie übergebenen Objekte ausgerichtet.
//chrome (v8)
var a = { 'test1':'test1val' }
test1 // error not defined
with (a) { var test1 = 'replaced' }
test1 // undefined
a // a.test1 = 'replaced'
EDIT: Klärendes Beispiel:
test1 hat einen Gültigkeitsbereich für den with-Block, ist jedoch auf a.test1 ausgerichtet. 'Var test1' erstellt eine neue Variable test1 im oberen lexikalischen Kontext (Funktion oder global), es sei denn, es ist eine Eigenschaft von a - was es ist.
Huch! Seien Sie vorsichtig mit 'with' - genau wie var ein Noop ist, wenn die Variable bereits in der Funktion definiert ist, ist es auch ein Noop in Bezug auf aus dem Objekt importierte Namen! Ein kleiner Hinweis auf den bereits definierten Namen würde dies viel sicherer machen. Ich persönlich werde aus diesem Grund nie mit verwenden.
with
Aussage ist aber eine Form des Block Scopingcatch
Klauseln sind eine viel häufigere Form (Fun fact, v8 implementiert catch
mit a with
) - das sind so ziemlich die einzigen Formen des Block-Scoping in JavaScript selbst ( dh Funktion, global, try / catch (mit und ihre Ableitungen) haben Host-Umgebungen jedoch unterschiedliche Vorstellungen von Scoping - zum Beispiel Inline-Ereignisse im Browser und im VM-Modul von NodeJS.
Ich habe festgestellt, dass viele JavaScript-Neulinge Probleme haben zu verstehen, dass die Vererbung standardmäßig in der Sprache verfügbar ist und dass der Funktionsumfang bisher der einzige Bereich ist. Ich habe eine Erweiterung für einen Verschönerer bereitgestellt, den ich Ende letzten Jahres geschrieben habe und der JSPretty heißt. Der Funktionsumfang der Feature-Farben im Code ordnet allen in diesem Bereich deklarierten Variablen immer eine Farbe zu. Das Schließen wird visuell demonstriert, wenn eine Variable mit einer Farbe aus einem Bereich in einem anderen Bereich verwendet wird.
Probieren Sie die Funktion unter:
Eine Demo finden Sie unter:
Den Code finden Sie unter:
Derzeit bietet die Funktion Unterstützung für eine Tiefe von 16 verschachtelten Funktionen, färbt jedoch derzeit keine globalen Variablen.
JavaScript hat nur zwei Arten von Gültigkeitsbereichen:
var
Schlüsselwort deklarierte Variable hat einen Funktionsumfang.Bei jedem Aufruf einer Funktion wird ein Variablenbereichsobjekt erstellt (und in die Bereichskette aufgenommen), auf das Variablen in JavaScript folgen.
a = "global";
function outer(){
b = "local";
console.log(a+b); //"globallocal"
}
outer();
Umfangskette ->
a
und outer
Funktion befinden sich in der Bereichskette auf oberster Ebene.variable scope object
(und in der Bereichskette enthaltene) Funktion mit einer darin enthaltenen Variablen b
aufruft.Wenn eine Variable a
erforderlich ist, sucht sie zuerst nach dem nächsten Variablenbereich und wenn die Variable nicht vorhanden ist, wird sie zum nächsten Objekt der Variablenbereichskette verschoben. In diesem Fall handelt es sich um die Fensterebene.
Um die anderen Antworten zu ergänzen, ist scope eine Nachschlageliste aller deklarierten Bezeichner (Variablen) und erzwingt strenge Regeln, wie diese für den aktuell ausgeführten Code zugänglich sind. Diese Suche kann zum Zuweisen der Variablen dienen, bei der es sich um eine LHS-Referenz (linke Seite) handelt, oder zum Abrufen ihres Werts, bei dem es sich um eine RHS-Referenz (rechte Seite) handelt. Diese Suchvorgänge werden von der JavaScript-Engine intern ausgeführt, wenn der Code kompiliert und ausgeführt wird.
Aus dieser Perspektive denke ich, dass ein Bild helfen würde, das ich im E-Book Scopes and Closures von Kyle Simpson gefunden habe:
Zitat aus seinem E-Book:
Das Gebäude repräsentiert den verschachtelten Regelsatz unseres Programms. Die erste Etage des Gebäudes repräsentiert Ihren aktuell ausgeführten Bereich, wo immer Sie sich befinden. Die oberste Ebene des Gebäudes ist die globale Reichweite. Sie lösen LHS- und RHS-Referenzen auf, indem Sie auf Ihre aktuelle Etage schauen. Wenn Sie sie nicht finden, fahren Sie mit dem Aufzug in die nächste Etage, suchen dort, dann in die nächste und so weiter. Sobald Sie die oberste Etage (den globalen Bereich) erreicht haben, finden Sie entweder das, wonach Sie suchen, oder Sie finden es nicht. Aber du musst trotzdem aufhören.
Eine erwähnenswerte Sache: "Die Scope-Suche wird beendet, sobald die erste Übereinstimmung gefunden wurde."
Diese Idee von "Bereichsebenen" erklärt, warum "dies" mit einem neu erstellten Bereich geändert werden kann, wenn er in einer verschachtelten Funktion nachgeschlagen wird. Hier ist ein Link, der auf all diese Details eingeht. Alles , was Sie über den Umfang von Javascript wissen wollten
Führen Sie den Code aus. Ich hoffe, dies gibt eine Vorstellung vom Scoping
Name = 'global data';
document.Name = 'current document data';
(function(window,document){
var Name = 'local data';
var myObj = {
Name: 'object data',
f: function(){
alert(this.Name);
}
};
myObj.newFun = function(){
alert(this.Name);
}
function testFun(){
alert("Window Scope : " + window.Name +
"\nLocal Scope : " + Name +
"\nObject Scope : " + this.Name +
"\nCurrent document Scope : " + document.Name
);
}
testFun.call(myObj);
})(window,document);
Globale Variablen sind genau wie globale Sterne (Jackie Chan, Nelson Mandela). Sie können von jedem Teil Ihrer Anwendung aus darauf zugreifen (den Wert abrufen oder festlegen). Globale Funktionen sind wie globale Ereignisse (Neujahr, Weihnachten). Sie können sie von jedem Teil Ihrer Anwendung aus ausführen (aufrufen).
//global variable
var a = 2;
//global function
function b(){
console.log(a); //access global variable
}
Wenn Sie in den USA sind, kennen Sie vielleicht Kim Kardashian, eine berüchtigte Berühmtheit (sie schafft es irgendwie, die Boulevardzeitungen zu machen). Aber Menschen außerhalb der USA werden sie nicht erkennen. Sie ist ein lokaler Star, der an ihr Territorium gebunden ist.
Lokale Variablen sind wie lokale Sterne. Sie können nur innerhalb des Bereichs auf sie zugreifen (den Wert abrufen oder festlegen). Eine lokale Funktion ist wie lokale Ereignisse - Sie können nur innerhalb dieses Bereichs ausführen (feiern). Wenn Sie von außerhalb des Bereichs darauf zugreifen möchten, wird ein Referenzfehler angezeigt
function b(){
var d = 21; //local variable
console.log(d);
function dog(){ console.log(a); }
dog(); //execute local function
}
console.log(d); //ReferenceError: dddddd is not defined
In diesem Artikel finden Sie detaillierte Informationen zum Umfang
Es gibt fast nur zwei Arten von JavaScript-Bereichen:
Andere Blöcke als Funktionen erstellen also keinen neuen Bereich. Dies erklärt, warum for-Schleifen Variablen mit äußerem Gültigkeitsbereich überschreiben:
var i = 10, v = 10;
for (var i = 0; i < 5; i++) { var v = 5; }
console.log(i, v);
// output 5 5
Verwenden Sie stattdessen Funktionen:
var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10
Im ersten Beispiel gab es keinen Blockbereich, sodass die ursprünglich deklarierten Variablen überschrieben wurden. Im zweiten Beispiel gab es aufgrund der Funktion einen neuen Bereich, sodass die ursprünglich deklarierten Variablen SHADOWED und nicht überschrieben wurden.
Das ist fast alles, was Sie in Bezug auf das JavaScript-Scoping wissen müssen, außer:
Sie können also sehen, dass das Scoping von JavaScript extrem einfach ist, wenn auch nicht immer intuitiv. Einige Dinge, die Sie beachten sollten:
Also dieser Code:
var i = 1;
function abc() {
i = 2;
var i = 3;
}
console.log(i); // outputs 1
ist äquivalent zu:
var i = 1;
function abc() {
var i; // var declaration moved to the top of the scope
i = 2;
i = 3; // the assignment stays where it is
}
console.log(i);
Dies mag kontraintuitiv erscheinen, ist jedoch aus der Sicht eines imperativen Sprachdesigners sinnvoll.
const
' und 'let
'Sie sollten Block Scoping für jede von Ihnen erstellte Variable verwenden, genau wie die meisten anderen Hauptsprachen. var
ist veraltet . Dies macht Ihren Code sicherer und wartbarer.
const
sollte in 95% der Fälle verwendet werden . Das macht es so die variable Referenz kann nicht geändert werden . Die Eigenschaften von Array-, Objekt- und DOM-Knoten können sich ändern und sollten es wahrscheinlich sein const
.
let
sollte für jede Variable verwendet werden, die eine Neuzuweisung erwartet. Dies schließt innerhalb einer for-Schleife ein. Wenn Sie den Wert über die Initialisierung hinaus ändern, verwenden Sielet
.
Blockbereich bedeutet, dass die Variable nur in den Klammern verfügbar ist, in denen sie deklariert ist. Dies gilt auch für interne Bereiche, einschließlich anonymer Funktionen, die in Ihrem Bereich erstellt wurden.
Versuchen Sie dieses merkwürdige Beispiel. Wenn im folgenden Beispiel a eine bei 0 initialisierte Zahl wäre, würden Sie 0 und dann 1 sehen. Außer a ist ein Objekt und Javascript übergibt f1 einen Zeiger von a und keine Kopie davon. Das Ergebnis ist, dass Sie beide Male dieselbe Warnung erhalten.
var a = new Date();
function f1(b)
{
b.setDate(b.getDate()+1);
alert(b.getDate());
}
f1(a);
alert(a.getDate());
In JS gibt es nur Funktionsbereiche. Bereiche nicht blockieren! Sie können sehen, was auch hebt.
var global_variable = "global_variable";
var hoisting_variable = "global_hoist";
// Global variables printed
console.log("global_scope: - global_variable: " + global_variable);
console.log("global_scope: - hoisting_variable: " + hoisting_variable);
if (true) {
// The variable block will be global, on true condition.
var block = "block";
}
console.log("global_scope: - block: " + block);
function local_function() {
var local_variable = "local_variable";
console.log("local_scope: - local_variable: " + local_variable);
console.log("local_scope: - global_variable: " + global_variable);
console.log("local_scope: - block: " + block);
// The hoisting_variable is undefined at the moment.
console.log("local_scope: - hoisting_variable: " + hoisting_variable);
var hoisting_variable = "local_hoist";
// The hoisting_variable is now set as a local one.
console.log("local_scope: - hoisting_variable: " + hoisting_variable);
}
local_function();
// No variable in a separate function is visible into the global scope.
console.log("global_scope: - local_variable: " + local_variable);
Mein Verständnis ist, dass es drei Bereiche gibt: globaler Bereich, global verfügbar; lokaler Bereich, der für eine gesamte Funktion unabhängig von Blöcken verfügbar ist; und Blockbereich, der nur für den Block, die Anweisung oder den Ausdruck verfügbar ist, für den er verwendet wurde. Der globale und lokale Bereich wird entweder innerhalb oder außerhalb einer Funktion mit dem Schlüsselwort 'var' angegeben, und der Blockbereich wird mit dem Schlüsselwort 'let' angegeben.
Für diejenigen, die glauben, dass es nur einen globalen und lokalen Bereich gibt, erklären Sie bitte, warum Mozilla eine ganze Seite haben würde, die die Nuancen des Blockbereichs in JS beschreibt.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Ein sehr häufiges Problem, auf das Front-End-Codierer noch nicht häufig stoßen, ist der Bereich, der für einen Inline-Ereignishandler im HTML-Code sichtbar ist - beispielsweise mit
<button onclick="foo()"></button>
Der Umfang der Variablen, auf die ein on*
Attribut verweisen kann, muss entweder sein:
querySelector
B. als eigenständige Variable , auf die verwiesen wird document.querySelector
; selten)Andernfalls erhalten Sie einen ReferenceError, wenn der Handler aufgerufen wird. Wenn der Inline-Handler beispielsweise auf eine Funktion verweist, die in window.onload
oder definiert ist, schlägt$(function() {
die Referenz fehl, da der Inline-Handler möglicherweise nur auf Variablen im globalen Bereich verweist und die Funktion nicht global ist:
Eigenschaften der document
und Eigenschaften des Elements der Handler angebracht sind kann auch als Standalone - Variablen innerhalb von Inline - Handler referenziert werden , da Inline - Handler aufgerufen werden , innerhalb von zwei with
Blöcken , einer für das document
, eine für das Element. Die Gültigkeitsbereichskette von Variablen in diesen Handlern ist äußerst unintuitiv , und ein Handler für Arbeitsereignisse erfordert wahrscheinlich eine globale Funktion (und unnötige globale Verschmutzung sollte wahrscheinlich vermieden werden ).
Da die Scope-Kette in Inline-Handlern so seltsam ist und Inline-Handler eine globale Verschmutzung benötigen, um zu funktionieren, und Inline-Handler manchmal hässliche Zeichenfolgen erfordern, die beim Übergeben von Argumenten entkommen, ist es wahrscheinlich einfacher, sie zu vermeiden. Fügen Sie stattdessen Ereignishandler mit Javascript (wie mit addEventListener
) und nicht mit HTML-Markup hinzu.
Anders als bei normalen <script>
Tags, die auf der obersten Ebene ausgeführt werden, wird Code in ES6-Modulen in einem eigenen privaten Bereich ausgeführt. Eine Variable, die oben in einem normalen <script>
Tag definiert ist, ist global, sodass Sie sie in anderen <script>
Tags wie folgt referenzieren können :
Die oberste Ebene eines ES6-Moduls ist jedoch nicht global. Eine Variable, die oben in einem ES6-Modul deklariert ist, ist nur in diesem Modul sichtbar, es sei denn, die Variable ist explizit export
bearbeitet oder einer Eigenschaft des globalen Objekts zugewiesen.
Die oberste Ebene eines ES6-Moduls ähnelt der des Inneren eines IIFE auf der obersten Ebene eines Normalen <script>
. Das Modul kann auf alle Variablen verweisen, die global sind, und nichts kann auf irgendetwas innerhalb des Moduls verweisen, es sei denn, das Modul ist explizit dafür ausgelegt.
In JavaScript gibt es zwei Arten von Gültigkeitsbereichen:
Die Funktion "Unten" verfügt über eine lokale Bereichsvariable carName
. Und auf diese Variable kann außerhalb der Funktion nicht zugegriffen werden.
function myFunction() {
var carName = "Volvo";
alert(carName);
// code here can use carName
}
Die unten stehende Klasse verfügt über eine globale Bereichsvariable carName
. Und auf diese Variable kann von überall in der Klasse zugegriffen werden.
class {
var carName = " Volvo";
// code here can use carName
function myFunction() {
alert(carName);
// code here can use carName
}
}
ES5
und früher:Variablen in Javascript waren anfangs (vor ES6
) lexikalisch funktionsbezogen. Der Begriff "lexikalisch" bedeutet, dass Sie den Umfang der Variablen sehen können, indem Sie den Code "betrachten".
Jede mit dem var
Schlüsselwort deklarierte Variable ist der Funktion zugeordnet. Wenn jedoch andere Funktionen innerhalb dieser Funktion deklariert sind, haben diese Funktionen Zugriff auf die Variablen der äußeren Funktionen. Dies wird als Scope-Kette bezeichnet . Es funktioniert folgendermaßen:
// global scope
var foo = 'global';
var bar = 'global';
var foobar = 'global';
function outerFunc () {
// outerFunc scope
var foo = 'outerFunc';
var foobar = 'outerFunc';
innerFunc();
function innerFunc(){
// innerFunc scope
var foo = 'innerFunc';
console.log(foo);
console.log(bar);
console.log(foobar);
}
}
outerFunc();
Was passiert, wenn wir versuchen, die Variablen und in der Konsole zu protokollieren foo
, ist Folgendes:bar
foobar
innerFunc
selbst. Daher wird der Wert von foo in die Zeichenfolge aufgelöst innerFunc
.innerFunc
selbst. Daher müssen wir die Zielfernrohrkette erklimmen . Wir schauen uns zuerst die äußere Funktion an, in der die Funktion innerFunc
definiert wurde. Dies ist die Funktion outerFunc
. Im Rahmen von finden outerFunc
wir die Variablenleiste, die die Zeichenfolge 'OuterFunc' enthält.ES6
(ES 2015) und älter:Die gleichen Konzepte von lexikalischem Umfang und Scopechain gelten immer noch in ES6
. Es wurden jedoch neue Möglichkeiten zur Deklaration von Variablen eingeführt. Es gibt Folgendes:
let
: Erstellt eine Variable mit Blockbereichconst
: Erstellt eine Variable mit Blockbereich, die initialisiert werden muss und nicht neu zugewiesen werden kannDer größte Unterschied zwischen var
und let
/ const
besteht darin, dass var
es sich um einen Funktionsbereich handelt, während let
/ einen const
Blockbereich hat. Hier ist ein Beispiel, um dies zu veranschaulichen:
let letVar = 'global';
var varVar = 'global';
function foo () {
if (true) {
// this variable declared with let is scoped to the if block, block scoped
let letVar = 5;
// this variable declared with let is scoped to the function block, function scoped
var varVar = 10;
}
console.log(letVar);
console.log(varVar);
}
foo();
Im obigen Beispiel protokolliert letVar den Wert global, da mit deklarierte Variablen einen let
Blockbereich haben. Sie existieren nicht mehr außerhalb ihres jeweiligen Blocks, sodass auf die Variable außerhalb des if-Blocks nicht zugegriffen werden kann.
In EcmaScript5 gibt es hauptsächlich zwei Bereiche, den lokalen Bereich und den globalen Bereich. In EcmaScript6 gibt es hauptsächlich drei Bereiche, den lokalen Bereich, den globalen Bereich und einen neuen Bereich, der als Blockbereich bezeichnet wird .
Beispiel für den Blockumfang ist: -
for ( let i = 0; i < 10; i++)
{
statement1...
statement2...// inside this scope we can access the value of i, if we want to access the value of i outside for loop it will give undefined.
}
ECMAScript 6 führte die Schlüsselwörter let und const ein. Diese Schlüsselwörter können anstelle des Schlüsselworts var verwendet werden. Im Gegensatz zum Schlüsselwort var unterstützen die Schlüsselwörter let und const die Deklaration des lokalen Bereichs innerhalb von Blockanweisungen.
var x = 10
let y = 10
const z = 10
{
x = 20
let y = 20
const z = 20
{
x = 30
// x is in the global scope because of the 'var' keyword
let y = 30
// y is in the local scope because of the 'let' keyword
const z = 30
// z is in the local scope because of the 'const' keyword
console.log(x) // 30
console.log(y) // 30
console.log(z) // 30
}
console.log(x) // 30
console.log(y) // 20
console.log(z) // 20
}
console.log(x) // 30
console.log(y) // 10
console.log(z) // 10
Die akzeptierte Antwort gefällt mir sehr gut, aber ich möchte Folgendes hinzufügen:
Scope sammelt und verwaltet eine Nachschlageliste aller deklarierten Bezeichner (Variablen) und erzwingt strenge Regeln, wie diese für aktuell ausgeführten Code zugänglich sind.
Der Bereich besteht aus einer Reihe von Regeln zum Nachschlagen von Variablen anhand ihres Bezeichnernamens.
In JavaScript gibt es zwei Arten von Bereichen.
Globaler Bereich : Variable, die im globalen Bereich angekündigt wird, kann überall im Programm sehr reibungslos verwendet werden. Zum Beispiel:
var carName = " BMW";
// code here can use carName
function myFunction() {
// code here can use carName
}
Funktionsbereich oder lokaler Bereich : Die in diesem Bereich deklarierte Variable kann nur in ihrer eigenen Funktion verwendet werden. Zum Beispiel:
// code here can not use carName
function myFunction() {
var carName = "BMW";
// code here can use carName
}