TLDR
JavaScript verfügt über lexikalisches (auch statisches) Scoping und Closures. Das bedeutet, dass Sie den Geltungsbereich eines Bezeichners erkennen können, indem Sie sich den Quellcode ansehen.
Die vier Geltungsbereiche sind:
- Global - für alle sichtbar
- Funktion - sichtbar innerhalb einer Funktion (und ihrer Unterfunktionen und Blöcke)
- Block - sichtbar innerhalb eines Blocks (und dessen Unterblöcken)
- Modul - sichtbar innerhalb eines Moduls
Außerhalb der Sonderfälle des globalen und des Modulbereichs werden Variablen mit var
(Funktionsumfang), let
(Blockumfang), und const
(Blockumfang). Die meisten anderen Formen der Bezeichnerdeklaration haben im Strict-Modus einen Blockbereich.
Übersicht
Der Geltungsbereich ist der Bereich der Codebasis, in dem ein Bezeichner gültig ist.
Eine lexikalische Umgebung ist eine Abbildung zwischen Bezeichnernamen und den mit ihnen verbundenen Werten.
Der Umfang besteht aus einer verknüpften Verschachtelung von lexikalischen Umgebungen, wobei jede Ebene der Verschachtelung einer lexikalischen Umgebung eines Vorgänger-Ausführungskontexts entspricht.
Diese verknüpften lexikalischen Umgebungen bilden eine "Scope-Kette". Die Bezeichnerauflösung ist der Prozess der Suche nach einem passenden Bezeichner entlang dieser Kette.
Die Auflösung von Identifikatoren erfolgt nur in einer Richtung: nach außen. Auf diese Weise können äußere lexikalische Umgebungen nicht in innere lexikalische Umgebungen "hineinsehen".
Drei Faktoren sind für die Entscheidung über die Umfang eines Kennung in JavaScript:
- Wie ein Identifikator deklariert wurde
- Wenn ein Identifikator deklariert wurde
- Ob Sie nun in strenger Modus o nicht-strenger Modus
Einige der Möglichkeiten, wie Bezeichner deklariert werden können:
var
, let
y const
- Parameter der Funktion
- Parameter des Fangblocks
- Funktionsdeklarationen
- Ausdrücke für benannte Funktionen
- Implizit definierte Eigenschaften des globalen Objekts (d. h. fehlende
var
im nicht-strikten Modus)
import
Aussagen
eval
Einige der Ortsbezeichnungen können angegeben werden:
- Globaler Kontext
- Funktion Körper
- Gewöhnlicher Block
- Der Anfang einer Kontrollstruktur (z.B. Schleife, if, while, etc.)
- Körper der Kontrollstruktur
- Module
Deklarationsstile
var
Bezeichner, die mit var
einen Funktionsumfang haben In diesem Fall werden sie als Eigenschaften des globalen Objekts hinzugefügt und haben einen globalen Geltungsbereich. Es gibt separate Regeln für ihre Verwendung in eval
Funktionen.
let und const
Bezeichner, die mit let
y const
einen Blockumfang haben Sie haben einen globalen Geltungsbereich, außer wenn sie direkt im globalen Kontext deklariert werden.
注 let
, const
y var
sind alle hochgezogen . Das bedeutet, dass ihre logische Definitionsposition am Anfang des sie umschließenden Bereichs (Block oder Funktion) liegt. Allerdings werden Variablen, die mit let
y const
kann erst gelesen oder zugewiesen werden, wenn die Kontrolle den Punkt der Deklaration im Quellcode passiert hat. Diese 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`!
Namen der Funktionsparameter
Funktionsparameternamen sind auf den Funktionskörper beschränkt. Beachten Sie, dass dies eine gewisse Komplexität mit sich bringt. Funktionen, die als Standardargumente deklariert sind, schließen über die Parameterliste und nicht den Körper der Funktion.
Funktionsdeklarationen
Funktionsdeklarationen haben im strikten Modus einen Blockbereich und im nicht-strikten Modus einen Funktionsbereich. Hinweis: Der nicht-strikte Modus ist ein komplizierter Satz sich entwickelnder Regeln, die auf den eigenartigen historischen Implementierungen der verschiedenen Browser basieren.
Ausdrücke für benannte Funktionen
Benannte Funktionsausdrücke sind auf sich selbst beschränkt (z. B. zum Zweck der Rekursion).
Implizit definierte Eigenschaften für das globale Objekt
Im nicht-strikten Modus haben implizit definierte Eigenschaften des globalen Objekts globalen Geltungsbereich, da das globale Objekt an der Spitze der Geltungskette steht. Im strikten Modus sind diese nicht zulässig.
eval
Sur eval
Zeichenketten, Variablen, die mit var
wird in den aktuellen Geltungsbereich gesetzt, oder, falls eval
wird indirekt als Eigenschaften des globalen Objekts verwendet.
Beispiele
Das Folgende führt zu einem ReferenceError, weil die Namen x
, y
y 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)
Das Folgende löst einen ReferenceError für y
y z
, aber nicht für x
weil die Sichtbarkeit von x
ist nicht durch den Block eingeschränkt. Blöcke, die die Körper von Kontrollstrukturen definieren, wie if
, for
y while
verhalten sich ähnlich.
{
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, weil var
hat Funktionsumfang:
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. Es gibt nur eine Instanz der Variablen x
deklariert und befindet sich logischerweise außerhalb der Schleife.
Die folgenden Ausdrucke 5
fünfmal, und druckt dann 5
ein sechstes Mal für die console.log
außerhalb der Schleife:
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
Die folgenden Ausdrucke undefined
denn x
ist block-scoped. Die Rückrufe werden einer nach dem anderen asynchron ausgeführt. Neues Verhalten für let
Variablen bedeutet, dass jede anonyme Funktion über eine andere Variable namens x
(anders als es bei var
), und damit ganze Zahlen 0
über 4
gedruckt 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
Das Folgende führt NICHT zu einem ReferenceError
weil die Sichtbarkeit von x
wird nicht durch den Block eingeschränkt; es wird jedoch gedruckt undefined
weil die Variable nicht initialisiert wurde (wegen 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
ist auf den Körper der Schleife beschränkt:
for(let x = 0; x < 10; ++x) {}
console.log(typeof x) // undefined, because `x` is block-scoped
Das Folgende löst eine ReferenceError
weil die Sichtbarkeit von x
wird durch den Block eingeschränkt:
if(false) {
let x = 1
}
console.log(typeof x) // undefined, because `x` is block-scoped
Variablen, die mit var
, let
o const
sind alle auf Module beschränkt:
// 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 Variablen, die mit var
innerhalb des globalen Kontexts werden dem globalen Objekt als Eigenschaften hinzugefügt:
var x = 1
console.log(window.hasOwnProperty('x')) // true
let
y const
im globalen Kontext fügen dem globalen Objekt keine Eigenschaften hinzu, haben aber dennoch globalen Geltungsbereich:
let x = 1
console.log(window.hasOwnProperty('x')) // false
Funktionsparameter können als im Funktionskörper deklariert angesehen werden:
function f(x) {}
console.log(typeof x) // undefined, because `x` is scoped to the function
Die Parameter des Catch-Blocks sind auf den Körper des Catch-Blocks beschränkt:
try {} catch(e) {}
console.log(typeof e) // undefined, because `e` is scoped to the catch block
Benannte Funktionsausdrücke sind nur auf den Ausdruck selbst beschränkt:
(function foo() { console.log(foo) })()
console.log(typeof foo) // undefined, because `foo` is scoped to its own expression
Im nicht-strikten Modus sind implizit definierte Eigenschaften des globalen Objekts global skaliert. Im strikten Modus erhalten Sie einen Fehler.
x = 1 // implicitly defined property on the global object (no "var"!)
console.log(x) // 1
console.log(window.hasOwnProperty('x')) // true
Im nicht-strikten Modus haben Funktionsdeklarationen einen Funktionsumfang. Im strikten Modus haben sie den Geltungsbereich eines Blocks.
'use strict'
{
function foo() {}
}
console.log(typeof foo) // undefined, because `foo` is block-scoped
Wie es unter der Haube funktioniert
Der Geltungsbereich ist definiert als die lexikalisch Bereich des Codes, für den ein Bezeichner gültig ist.
In JavaScript hat jedes Funktionsobjekt eine versteckte [[Environment]]
Referenz, die eine Referenz auf den lexikalische Umgebung de la Ausführungskontext (Stapelrahmen), in dem er erstellt wurde.
Wenn Sie eine Funktion aufrufen, wird die versteckte [[Call]]
Methode aufgerufen wird. Diese Methode erzeugt einen neuen Ausführungskontext und stellt eine Verbindung zwischen dem neuen Ausführungskontext und der lexikalischen Umgebung des Funktionsobjekts her. Dies geschieht durch Kopieren der [[Environment]]
Wert des Funktions-Objekts in eine äußerer Bezug in der lexikalischen Umgebung des neuen Ausführungskontexts.
Beachten Sie, dass diese Verbindung zwischen dem neuen Ausführungskontext und der lexikalischen Umgebung des Funktionsobjekts als Verschluss .
In JavaScript wird der Geltungsbereich also durch lexikalische Umgebungen implementiert, die durch äußere Referenzen in einer "Kette" miteinander verbunden sind. Diese Kette von lexikalischen Umgebungen wird als Scope-Kette bezeichnet, und die Auflösung von Bezeichnern erfolgt durch Suche in der Kette nach einem passenden Bezeichner.
Herausfinden mehr .
5 Stimmen
Hier ist eine weitere schöne Link dieses Thema im Auge zu behalten: " Erläuterung von JavaScript-Bereich und -Schließungen ".
10 Stimmen
Hier ist ein Artikel, der dies sehr gut erklärt. Alles, was Sie über den Umfang von Javascript-Variablen wissen müssen
3 Stimmen
Le site zuvor erwähnt E-Book von Kyle Simpson ist auf Github zu lesen, und es sagt Ihnen alles, was Sie über JavaScript Scopes & Closures wissen müssen. Sie können es hier finden: github.com/getify/You-Dont-Know-JS/blob/master/ Es ist Teil des Buchreihe "Du kennst JS nicht" die für alle geeignet ist, die mehr über JavaScript wissen möchten.
0 Stimmen
var
Regeln. javascript braucht nicht den "Zusatz" von 'const' und 'let', die gegen seinen Geist sind. - Ich weiß, dass diese beiden nicht Teil Ihrer Frage sind - musste dies hinzufügen, nachdem ich sah, dass so viele sie "schieben".