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:
- 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).
- Dokumenterer dens uforanderlige natur for andre programmerere.
- Lar JavaScript-motoren optimalisere basert på at verdien til
constikke 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? falsedelete 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); // 0Dette 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:
vargjelder alltid for hele eksekveringskonteksten (gjennom hele global kode, eller gjennom hele funksjonskoden i funksjonen der det dukker opp; det hopper ut av blokker), menletogconstgjelder bare innenfor blokken der de dukker opp. Det vil si atvarhar funksjons- (eller global) rekkevidde, menletogconsthar blokk-rekkevidde.Å gjenta
var ai samme kontekst er ufarlig, men hvis du harlet a(ellerconst a), vil det å ha en annenlet aeller enconst aeller envar avæ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
undefinedogNaN, som, merkelig nok, er globale snarere enn nøkkelord; diverse globale funksjoner) - (I nettlesere) Variabler for alle DOM-elementer med en
idog mange med enname(forutsatt atid/name-verdien er et gyldig identifikator; ellers er de bare egenskaper påwindowmen ikke globale variabler) - (I nettlesere) Variabler for
window-spesifikke ting, somname,location,self... - Variabler for alle
var-setninger på globalt nivå - Variabler for alle globale
let,constogclass-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'
