Forskjell mellom syntaks for variabeldeklarasjon i Javascript (inkludert globale variabler)?


Er det noen forskjell mellom å deklarere en variabel:

var a=0; //1

...på denne måten:

a=0; //2

...eller:

window.a=0; //3

i globalt omfang?

Svar #1

Ja, det er et par forskjeller, selv om de i praksis vanligvis ikke er store (bortsett fra nummer 2 — a=0; — som A) jeg sterkt anbefaler å ikke gjøre, og B) er en feil i strikt modus).

Det finnes en fjerde måte, og fra ES2015 (ES6) finnes det to til. Jeg har lagt til den fjerde måten til slutt, men satt inn ES2015-måtene etter #1 (du vil se hvorfor), så vi har:

var a = 0;                             // 1
let a = 0;                             // 1.1 (new with ES2015)
const a = 0;                           // 1.2 (new with ES2015)
a = 0;                                 // 2
window.a = 0; /*or*/ globalThis.a = 0; // 3
this.a = 0;                            // 4

De uttalelsene forklarte

1. var a=0;

Dette oppretter en global variabel som også er en egenskap til det globale objektet, som vi får tilgang til som window i nettlesere (eller via globalThis-globalen som ble lagt til i ES2020, eller via this i globalt omfang). I motsetning til noen andre egenskaper, kan ikke denne egenskapen fjernes med delete.

I spesifikasjonstermer oppretter det en identifikatorbinding på objektsmiljøoppføringen for det globale miljøet. Det gjør det til en egenskap ved det globale objektet fordi det globale objektet er der identifikatorbindinger for det globale miljøets objektsmiljøoppføring holdes. Dette er grunnen til at egenskapen ikke kan slettes: Det er ikke bare en enkel egenskap, det er en identifikatorbinding, og identifikatorer kan ikke fjernes.

Bindingen (variabelen) er definert før den første kodelinjen kjører (se «Når var skjer» nedenfor).

Egenskapen dette skaper er opplistbar (bortsett fra på den svært utdaterte IE8 og tidligere).


1.1 la a=0;

Dette skaper en global variabel som ikke er en egenskap av det globale objektet. Dette er en ny ting fra og med ES2015.

I spesifikasjonstermer oppretter det en identifikatorbinding på Declarative Environment Record for det globale miljøet i stedet for Object Environment Record. Det globale miljøet er unikt ved å ha en delt Environment Record, én for alle de gamle tingene som går på det globale objektet (the Object Environment Record) og en annen for alle de nye tingene (let, const, og funksjonene opprettet av class) som ikke går på det globale objektet, men går i stedet inn i det globale miljøets Declarative Environment Record.

Bindingen blir opprettet før noen trinn-for-trinn-kode i den innkapslende blokken blir utført (i dette tilfellet før noen global kode kjører), men den er ikke tilgjengelig på noen måte før trinn-for-trinn-utførelsen når let-setningen. Når utførelsen når let-setningen, blir variabelen tilgjengelig. (Se «Når let og const skjer» nedenfor.) Tiden mellom at bindingen blir opprettet (ved inngang til scopet) og blir tilgjengelig (kodeutførelsen når let) kalles Temporal Dead Zone [TMZ]. Mens bindingen er i den tilstanden, vil ethvert forsøk på å lese fra den eller skrive til den medføre en kjøretidsfeil.

(Spesifikasjonens terminologi for om bindingen er tilgjengelig er om den er «initialisert», men ikke forveksle bruken av «initialisert» med å ha en initialisator på let-setningen [let a=10; vs. bare let a;]; de er ikke relatert. Variabelen definert av la a; initialiseres med udefinert når let er nådd.)


1.2 const a=0;

Oppretter en global konstant, som ikke er en egenskap av det globale objektet.

En const-binding er helt lik en let-binding (inkludert TMZ og slike ting) bortsett fra at den har et flagg som sier at verdien ikke kan endres. En konsekvens av det er at du må oppgi en initialiserer (delen = verdi) for å gi den første (og aldri-endrede) verdien for const.

Å bruke const gjør tre ting for deg:

  1. Gjør det til en kjøretidsfeil hvis du prøver å tilordne til konstanten (og de fleste IDE-er vil varsle deg om det mer proaktivt enn det).
  2. Dokumenterer dens uforanderlige natur for andre programmerere.
  3. Lar JavaScript-motoren optimalisere basert på at verdien til const ikke vil endre seg (uten å måtte spore om den blir skrevet til senere eller ikke — f.eks. trenger ikke å sjekke om den er effektivt konstant).

Det er viktig å forstå at det at verdien til const aldri endres, ikke betyr at et objekt som const refererer til, er uforanderlig. Det er det ikke. Det betyr bare at verdien til const ikke kan endres slik at det refererer til et annet objekt (eller inneholder et primitivt):

// This is fine:
const x1 = {a: 1};
console.log(x1.a); // 1
x1.a = 2;
//^^^^^^−−− No problem, just changing the object's state, not the value in the `const` (the object reference)
console.log(x1.a); // 2

// This is not:
const x2 = {a: 1};
console.log(x2.a); // 1
x2 = {a: 2};
// ^−−−−−−− Error here ("TypeError: Assignment to constant variable"),
// you can't change the value of a `const`
console.log(x2.a);

2 a=0;

Ikke gjør dette. 😊 Det er å tildele en helt udeklarert identifikator. I løs modus (den eneste modusen før ES5), oppretter det en egenskap på det globale objektet implisitt. På min gamle blogg kaller jeg dette Skrekken ved implisitte globale. Heldigvis fikset de det med streng modus, lagt til i ES5 og standard i nye typer områder (inne i moduler, inne i class-konstruksjoner, osv.). Streng modus gjør tildeling til en udeklert identifikator til den feilen det alltid burde ha vært. Det er en av flere grunner til å bruke streng modus.

Siden det oppretter en normal egenskap, kan du delete den.

Egenskapen dette oppretter er opplistbar (bortsett fra på den veldig utdaterte IE8 og eldre).


3 window.a=0; eller globalThis.a=0;

Dette oppretter en egenskap på det globale objektet eksplisitt, ved å bruke window global (i nettlesere) eller globalThis global som refererer til det globale objektet. Siden det er en normal egenskap, kan du slette den.

Denne egenskapen er opplistbar (selv på den svært utdaterte IE8 og tidligere).


4 this.a=0;

Nøyaktig som #3, bortsett fra at vi refererer til det globale objektet gjennom this i stedet for globale window eller globalThis. Dette fungerer fordi this på globalt nivå er verdien av «globalt» this. Dette er sant selv i streng modus. (Streng modus endrer this som brukes når du kaller en funksjon uten å angi this, slik som når du gjør fn(), men ikke hva this er på globalt nivå.) Merk at det virkelig må være globalt nivå. Topplagsnivået for moduler er ikke globalt nivå (det er modul-nivå), og på modulnivå er this undefined.


Sletting av egenskaper

Hva mener jeg med «slette» eller «fjerne» a? Akkurat det: Å fjerne egenskapen (fullstendig) via delete-nøkkelordet:

window.a = 0;
console.log(`"a" in window? ${"a" in window}`); // "a" in window? true
delete window.a;
console.log(`"a" in window? ${"a" in window}`); // "a" in window? false

delete fjerner en egenskap fullstendig fra et objekt. Du kan ikke gjøre det med egenskaper lagt til window indirekte via var, delete blir enten stille ignorert eller kaster en unntak (avhengig av om du er i streng modus).

(Liten merknad: Den svært utdaterte IE8 og tidligere, og den utdaterte IE9-IE11 i deres ødelagte «kompatibilitets»-modus, ville ikke la deg slette window-egenskaper selv om du skulle ha hatt lov til det.)


Når var skjer

Forord: var har ingen plass i ny kode. Bruk let eller const i stedet. Men det er nyttig å forstå var for å kunne forstå gammel kode du kommer over.

Variablene definert via var-setningen opprettes før noen steg-for-steg-kode i utføringskonteksten kjøres, og derfor eksisterer variabelen (og dens egenskap på det globale objektet) godt før var-setningen.

Dette kan være forvirrende, så la oss ta en titt. Her har vi kode som prøver å få tilgang til a og b, etterfulgt av kode i midten som oppretter dem, og deretter kode som prøver å få tilgang til dem igjen:

try {
    console.log(a);   // undefined
    console.log(b);   // ReferenceError: b is not defined
} catch (e) {
    console.error(e);
}

var a = "ayy";
b = "bee";            // Don't do this, but I didn't want to use `let` or `const` in this example

try {
    console.log(a);   // "ayy"
    console.log(b);   // "bee"
} catch (e) {
    console.error(e);
}

Som du kan se, er identifikatoren a definert (med verdien undefined) før den første linjen kjøres, men identifikatoren b er det ikke, så å prøve å lese verdien dens gir en ReferenceError. Utsagnet var a="ayy"; gjør faktisk to forskjellige ting, på forskjellige tidspunkter: Ved innføring i rekkevidden definerer det identifikatoren med startverdien undefined (delen var a), og senere, når det nås under kjøringen av koden, setter det verdien av a (delen a="ayy"). Siden a er definert før den første kodelinjen kjøres, kan vi bruke den (og se dens udefinerte verdi). Dette kalles "var-heising" fordi var a-delen flyttes ("heises") til toppen av det globale teleskopet eller funksjonsomfanget der den vises, men a="ayy"-delen blir stående på sin opprinnelige plassering. (Se Stakkars misforstått var på min anemiske gamle blogg.)


Når let og const skjer

let og const er forskjellige fra var på et par nyttige måter. Måtene som er relevante for spørsmålet er A) at selv om bindingen de definerer opprettes før noen steg-for-steg-kode kjører, er den ikke tilgjengelig før let eller const-setningen nås; og B) som vi har sett ovenfor, på globalt nivå oppretter de ikke egenskaper på det globale objektet.

Når det gjelder (A), mens dette som bruker var kjører:

console.log(a); // undefined
var a = 0;
console.log(a); // 0

Dette bruker let kaster en feil:

console.log(a); // ReferenceError: a is not defined
let a = 0;
console.log(a);

De to andre måtene som let og const skiller seg fra var på, som egentlig ikke er relevante for spørsmålet, er:

  1. var gjelder alltid for hele eksekveringskonteksten (gjennom hele global kode, eller gjennom hele funksjonskoden i funksjonen der det dukker opp; det hopper ut av blokker), men let og const gjelder bare innenfor blokken der de dukker opp. Det vil si at var har funksjons- (eller global) rekkevidde, men let og const har blokk-rekkevidde.

  2. Å gjenta var a i samme kontekst er ufarlig, men hvis du har let a (eller const a), vil det å ha en annen let a eller en const a eller en var a være en syntaksfeil.

Her er et eksempel som viser at let og const trer i kraft umiddelbart i sin blokk før noen kode i den blokken kjøres, men ikke er tilgjengelige før let- eller const-setningen:

let a = 0;          // (Doesn't matter whether this is `let`, `const`, or `var` [or even `class` or `function`])
console.log(a);     // 0
if (true) {
    console.log(a); // ReferenceError: a is not defined
    let a = 1;
    console.log(a);
}

Merk at den andre console.log mislykkes, i stedet for å få tilgang til a fra utenfor blokken, fordi innenfor den blokken refererer a til a som er deklarert senere i blokken. Men console.log-setningen forekommer innenfor den temporale dødszonen for den indre a, så det forårsaker en feil.


Unngå å fylle globalt område - bruk moduler

Det globale området er veldig, veldig rotete. Det har (minst):

  • Mange globale variabler opprettet via spesifikasjonen (som undefined og NaN, som, merkelig nok, er globale snarere enn nøkkelord; diverse globale funksjoner)
  • (I nettlesere) Variabler for alle DOM-elementer med en id og mange med en name (forutsatt at id/name-verdien er et gyldig identifikator; ellers er de bare egenskaper på window men ikke globale variabler)
  • (I nettlesere) Variabler for window-spesifikke ting, som name, location, self...
  • Variabler for alle var-setninger på globalt nivå
  • Variabler for alle globale let, const og class-setninger

Alle disse globale variablene er fulle av muligheter for konflikter med koden din, som dette klassiske eksempelet på nettlesere:

var name = 42;
console.log(name);          // 42 - seems okay, but...
console.log(typeof name);   // ...string?!?!!

Hvorfor er name en streng? Fordi det er en tilkomst-egenskap på window for navnet til vindusobjektet, som alltid er en streng. (Det tilsvarende med let ville fungert som forventet, siden den deklarative miljøposten konseptuelt er nestet innenfor objektmiljøposten, så name-bindingen opprettet med let skygger for name-bindingen for tilkomst-egenskapen.)

Når det er mulig, ikke bidra til rotet. Bruk moduler i stedet. Topplevelsesområdet i moduler er modulescope, ikke globalt område, så bare annen kode i modulen din ser disse toppnivådeklarasjonene. Du kan dele informasjon mellom moduler via export og import.

Før moduler brukte vi "scoping"-funksjoner pakket rundt koden vår:

// From the ES5 era
(function () {
    var a = 0; // `a` is NOT a property of `window` now

    function example() {
        console.log(a);   // Shows "0", because `example` can access `a`
    }
    
    example();
})();

Moduler gjør det foreldet.

Svar #2

Holder det enkelt :

a = 0

Koden ovenfor gir en global omfangsvariabel

var a = 0;

Denne koden vil gi en variabel som kan brukes i det nåværende omfanget, og under det

window.a = 0;

Dette er vanligvis det samme som den globale variabelen.

Svar #3

<title>Index.html</title>
<script>
    var varDeclaration = true;
    noVarDeclaration = true;
    window.hungOnWindow = true;
    document.hungOnDocument = true;
</script>
<script src="external.js"></script>

/* external.js */

console.info(varDeclaration == true); // could be .log, alert etc
// returns false in IE8

console.info(noVarDeclaration == true); // could be .log, alert etc
// returns false in IE8

console.info(window.hungOnWindow == true); // could be .log, alert etc
// returns true in IE8

console.info(document.hungOnDocument == true); // could be .log, alert etc
// returns ??? in IE8 (untested!)  *I personally find this more clugy than hanging off window obj

Finnes det et globalt objekt som alle variabler henger på som standard? f.eks: 'globals.noVar-erklæring'

Relaterte artikler