Różnica między składniami deklaracji zmiennych w Javascript (w tym zmiennymi globalnymi)?

292

Czy istnieje jakakolwiek różnica między deklarowaniem zmiennej:

var a=0; //1

...tą drogą:

a=0; //2

...lub:

window.a=0; //3

w zasięgu globalnym?

Dan
źródło
2
AFAIK var a = 0; nie działa w IE podczas uzyskiwania dostępu do zmiennej przez inny zewnętrzny plik js, który jest zadeklarowany w innym pliku js
Aivan Monceller
Nie wiem o window.a, ale pozostałe 2 sposoby są takie same w zakresie globalnym.
programista
1
@AivanMonceller naprawdę? link proszę.
Raynos
@Raynos, doświadczam tego na własnej stronie internetowej. Mówiąc konkretnie, IE6. Nie mogę wyświetlić mojego var enum, który znajduje się na zewnętrznym pliku js i
odnoszę się
@Ashwini W zakresie globalnym okno jest obiektem globalnym (w przeglądarkach). var a = 1; console.log (a); console.log (win
leebriggs

Odpowiedzi:

557

Tak, istnieje kilka różnic, choć w praktyce nie są one zwykle duże.

Jest czwarty sposób, a od ES2015 (ES6) są jeszcze dwa. Na końcu dodałem czwarty sposób, ale wstawiłem ES2015 po nr 1 (zobaczysz dlaczego), więc mamy:

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;  // 3
this.a = 0;    // 4

Te oświadczenia zostały wyjaśnione

# 1 var a = 0;

Tworzy to zmienną globalną, która jest także własnością obiektu globalnego , do którego uzyskujemy dostęp jak windoww przeglądarkach (lub za pośrednictwem thiszasięgu globalnego, w nieskomplikowanym kodzie). W przeciwieństwie do niektórych innych właściwości, właściwości nie można usunąć za pośrednictwem delete.

Pod względem specyfikacji, tworzy identyfikator wiążącą na obiekcie Środowisko Record dla globalnego środowiska . To sprawia, że ​​jest to właściwość obiektu globalnego, ponieważ obiekt globalny jest miejscem, w którym przechowywane są powiązania identyfikatorów obiektu środowiska globalnego Record Environment. Właśnie dlatego tej właściwości nie można usunąć: nie jest to zwykła właściwość, ale także powiązanie identyfikatora.

Wiązanie (zmienne) jest definiowane przed uruchomieniem pierwszego wiersza kodu (patrz „Kiedy się varstanie” poniżej).

Zauważ, że w IE8 i wcześniejszych utworzona właściwość windownie jest wyliczalna (nie pojawia się w for..ininstrukcjach). W IE9, Chrome, Firefox i Opera jest wymienny.


# 1.1 let a = 0;

Tworzy to zmienną globalną, która nie jest własnością obiektu globalnego. To nowość od ES2015.

Pod względem specyfikacji tworzy identyfikator wiążący deklaratywny rekord środowiska dla środowiska globalnego, a nie obiektowy rekord środowiska. Środowisko globalny jest wyjątkowy o podział Środowisko Record, jeden za wszystkich starych rzeczy, które idzie na światowym obiektu ( obiekt Środowisko Record), a inny dla wszystkich nowych rzeczy ( let, consti funkcje stworzone przez class), które nie przejdź do obiektu globalnego.

Powiązanie jest tworzone przed wykonaniem dowolnego kodu krok po kroku w jego otaczającym bloku (w tym przypadku przed uruchomieniem dowolnego kodu globalnego), ale nie jest dostępne w żaden sposób, dopóki wykonanie instrukcji krok po kroku nie osiągnie letinstrukcji. Gdy wykonanie osiągnie letinstrukcję, zmienna jest dostępna. (Zobacz „Kiedy leti co się conststanie” poniżej.)


# 1.2 const a = 0;

Tworzy stałą globalną, która nie jest własnością obiektu globalnego.

constjest dokładnie tak samo, letz tym wyjątkiem, że musisz podać inicjator ( = valueczęść) i nie możesz zmienić wartości stałej po jej utworzeniu. Pod okładkami jest dokładnie tak, letale z flagą na identyfikatorze wiążącą, że jego wartości nie można zmienić. Korzystanie constrobi dla Ciebie trzy rzeczy:

  1. Sprawia, że ​​jest to błąd czasu analizy, jeśli próbujesz przypisać stałą.
  2. Dokumentuje niezmienny charakter dla innych programistów.
  3. Pozwala zoptymalizować silnik JavaScript na podstawie, że się nie zmieni.

# 2 a = 0;

Spowoduje to niejawne utworzenie właściwości obiektu globalnego . Ponieważ jest to normalna właściwość, możesz ją usunąć. Nie polecam tego robić, może być niejasne dla osób czytających Twój kod później. Jeśli używasz trybu ścisłego ES5, wykonanie tego (przypisanie do nieistniejącej zmiennej) jest błędem. Jest to jeden z kilku powodów, dla których warto stosować tryb ścisły.

Co ciekawe, ponownie w IE8 i wcześniejszych utworzona właściwość jest niepoliczalna (nie pojawia się w for..ininstrukcjach). To dziwne, szczególnie biorąc pod uwagę nr 3 poniżej.


# 3 window.a = 0;

Spowoduje to jawne utworzenie właściwości obiektu globalnego przy użyciu obiektu windowglobalnego, który odnosi się do obiektu globalnego (w przeglądarkach; niektóre środowiska inne niż przeglądarka mają równoważną zmienną globalną, na przykład globalNodeJS). Ponieważ jest to normalna właściwość, możesz ją usunąć.

Ta właściwość jest wyliczalna w IE8 i wcześniejszych wersjach oraz w każdej innej przeglądarce, której próbowałem.


# 4 this.a = 0;

Dokładnie jak w punkcie 3, z tym wyjątkiem, że odwołujemy się do globalnego obiektu thiszamiast globalnego window. Nie działa to jednak w trybie ścisłym, ponieważ w trybie ścisłym kod globalny thisnie ma odniesienia do obiektu globalnego ( undefinedzamiast tego ma wartość ).


Usuwanie właściwości

Co rozumiem przez „usuwanie” lub „usuwanie” a? Dokładnie to: usunięcie właściwości (całkowicie) za pomocą deletesłowa kluczowego:

window.a = 0;
display("'a' in window? " + ('a' in window)); // displays "true"
delete window.a;
display("'a' in window? " + ('a' in window)); // displays "false"

deletecałkowicie usuwa właściwość z obiektu. Nie możesz tego zrobić z właściwościami dodanymi windowpośrednio przez var, deletealbo jest on cicho ignorowany, albo zgłasza wyjątek (w zależności od implementacji JavaScript i tego, czy jesteś w trybie ścisłym).

Ostrzeżenie : IE8 ponownie (i prawdopodobnie wcześniej, i IE9-IE11 w zepsutym trybie „kompatybilności”): Nie pozwoli ci usunąć właściwości windowobiektu, nawet jeśli powinieneś. Co gorsza, rzuca wyjątek podczas próby ( spróbuj tego eksperymentu w IE8 i innych przeglądarkach). Więc usuwając windowobiekt, musisz być defensywny:

try {
    delete window.prop;
}
catch (e) {
    window.prop = undefined;
}

Próbuje usunąć właściwość, a jeśli zostanie zgłoszony wyjątek, robi następną najlepszą rzecz i ustawia właściwość na undefined.

Odnosi się to tylko do windowobiektu i tylko (o ile mi wiadomo) do IE8 i wcześniejszych (lub IE9-IE11 w niedziałającym trybie „kompatybilności”). Inne przeglądarki mogą usuwać windowwłaściwości zgodnie z powyższymi zasadami.


Kiedy to się varstanie

Zmienne zdefiniowane przez varoświadczenia są tworzone przed każdy prowadzony jest kod krok po kroku w kontekście realizacji, a więc własność istnieje dobrze przed tym varstwierdzeniem.

Może to być mylące, więc spójrzmy:

display("foo in window? " + ('foo' in window)); // displays "true"
display("window.foo = " + window.foo);          // displays "undefined"
display("bar in window? " + ('bar' in window)); // displays "false"
display("window.bar = " + window.bar);          // displays "undefined"
var foo = "f";
bar = "b";
display("foo in window? " + ('foo' in window)); // displays "true"
display("window.foo = " + window.foo);          // displays "f"
display("bar in window? " + ('bar' in window)); // displays "true"
display("window.bar = " + window.bar);          // displays "b"

Przykład na żywo:

Jak widać, symbol foojest zdefiniowany przed pierwszą linią, ale symbol barnie jest. Tam, gdzie var foo = "f";znajduje się instrukcja, tak naprawdę są dwie rzeczy: zdefiniowanie symbolu, co dzieje się przed uruchomieniem pierwszego wiersza kodu; i wykonanie przypisania do tego symbolu, co dzieje się tam, gdzie linia przebiega krok po kroku. Nazywa się to „ varpodnoszeniem”, ponieważ var fooczęść jest przenoszona („podnoszona”) na szczyt zakresu, ale foo = "f"część pozostaje w pierwotnym położeniu. (Zobacz Biedny źle zrozumianyvar na moim anemicznym małym blogu.)


Kiedy leti się constzdarzy

leti constróżnią się od siebie varna kilka sposobów. Istotne dla pytania jest to, że chociaż powiązanie, które definiują, jest tworzone przed uruchomieniem kodu krok po kroku, nie jest ono dostępne, dopóki nie zostanie osiągnięta instrukcja letlub const.

Podczas gdy to działa:

display(a);    // undefined
var a = 0;
display(a);    // 0

To powoduje błąd:

display(a);    // ReferenceError: a is not defined
let a = 0;
display(a);

Pozostałe dwa sposoby, leta constróżnią się var, co tak naprawdę nie są istotne z punktu widzenia pytanie, to:

  1. varzawsze odnosi się do całego kontekstu wykonania (w całym kodzie globalnym, lub całej kodu funkcji w zależności gdzie się pojawia), ale leti conststosować tylko w bloku , w którym się pojawiają. Oznacza to, że varposiada funkcję (lub globalne) zakres, ale leti constmieć zakres bloku.

  2. Powtarzanie var aw tym samym kontekście jest nieszkodliwe, ale jeśli masz let a(lub const a), posiadanie innego let alub a const alub a var ajest błędem składni.

Oto przykład, który to pokazuje leti conststosuje się natychmiast w ich bloku, zanim uruchomi się jakikolwiek kod w tym bloku, ale nie są one dostępne, dopóki instrukcja letlub const:

var a = 0;
console.log(a);
if (true)
{
  console.log(a); // ReferenceError: a is not defined
  let a = 1;
  console.log(a);
}

Zauważ, że drugi console.logkończy się niepowodzeniem, zamiast uzyskiwać dostęp az zewnątrz bloku.


Off-topic: Unikaj zaśmiecania obiektu globalnego ( window)

windowObiekt staje się bardzo, bardzo zaśmiecone właściwości. Jeśli to możliwe, zdecydowanie nie dodawaj bałaganu. Zamiast tego zawiń symbole w małą paczkę i wyeksportuj maksymalnie jeden symbol do windowobiektu. (Często nie eksportuję żadnych symboli do windowobiektu.) Możesz użyć funkcji, która zawiera cały kod w celu przechowywania symboli, a funkcja ta może być anonimowa, jeśli chcesz:

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

    function foo() {
        alert(a);   // Alerts "0", because `foo` can access `a`
    }
})();

W tym przykładzie definiujemy funkcję i uruchamiamy ją od razu ( ()na końcu).

Używana w ten sposób funkcja jest często nazywana funkcją określania zakresu . Funkcje zdefiniowane w funkcji scoping mogą uzyskać dostęp do zmiennych zdefiniowanych w funkcji scoping, ponieważ są zamknięciami w stosunku do tych danych (patrz: Zamknięcia nie są skomplikowane na moim anemicznym blogu).

TJ Crowder
źródło
czy mogę to zrobić, window['a']=0aby wyjaśnić, że używam okna jako mapy? jest windowwyjątkowy, ponieważ niektóre przeglądarki na to nie pozwalają i zmuszają mnie do używania window.a?
Jayen
Jedna uwaga na punkcie 3, którą prawdopodobnie warto wyjaśnić: window.a = 0;działa tylko w środowiskach przeglądarki i tylko zgodnie z konwencją. Wiązania globalnego obiektu do zmiennej o nazwie windownie jest w ES Spec i tak nie będzie działać w, na przykład, V8 lub node.js, podczas gdy this.a = 0;(przy wywołaniu w kontekście globalnym wykonania) będzie działać w każdym środowisku, ponieważ spec ma określić że musi istnieć globalny obiekt. W przypadku zawijania kodu w IIFE, jak w sekcji Off-topic , można przekazać jako parametr o nazwie lub uzyskać bezpośrednie odwołanie do obiektu globalnego. thiswindowglobal
Sherlock_HJ
@Sherlock_HJ: Dodałem „w przeglądarkach”; jest to również wcześniej w odpowiedzi, ale dodałem ją na wypadek, gdyby ludzie się do tego pominęli. Jest teraz w specyfikacji ; chociaż jest to tylko przemijanie, nie znajdziesz przeglądarki, która tego nie robi. Jestem nieco zaskoczony, że nie jest w załączniku B .
TJ Crowder,
@TJCrowder, więc zmienna globalna zadeklarowana za pomocą var a = 0;automatycznie staje się własnością obiektu globalnego. Jeśli zadeklaruję var b = 0;w ramach deklaracji funkcji, czy będzie ona również własnością jakiegoś obiektu bazowego?
ezpresso
@ezpresso: Nie i tak. Oni stają się właściwości obiektu (w EnvironmentRecord z VariableEnvironment z ExecutionContext gdzie się pojawiają; szczegóły tutaj i tutaj ), ale nie ma sposobu, aby uzyskać bezpośredni dostęp, że obiekt z kodu programu.
TJ Crowder
40

Prostota:

a = 0

Powyższy kod podaje globalną zmienną zakresu

var a = 0;

Ten kod da zmienną do użycia w bieżącym zakresie i pod nim

window.a = 0;

Zasadniczo jest to to samo co zmienna globalna.

Umair Jabbar
źródło
Twoje stwierdzenia „Powyższy kod daje globalną zmienną zakresu” i „Ten kod da zmienną do użycia w bieżącym zakresie, a pod nim” razem wzięte sugerują, że nie można użyć pierwszego wiersza i dostępu a w ramach obecny zakres. Możesz. Ponadto użycie „zmiennej globalnej” jest nieco wyłączone - dwa miejsca, które mówisz „zmienna globalna”, nie są bardziej globalne niż miejsce, w którym nie mówisz.
TJ Crowder
sam globalny oznacza, że ​​możesz uzyskać dostęp / odczyt / zapis zmiennej w dowolnym miejscu, w tym w miejscu, w którym wspomniałem o obecnym zakresie, to takie oczywiste. A jeśli zasugerujesz, że window.a i 'a' nie będą globalne w skrypcie, to w 100% się mylisz.
Umair Jabbar
3
@Umair: „sam globalny oznacza, że ​​możesz uzyskać dostęp / odczyt / zapis zmiennej w dowolnym miejscu” . Prawo. Znów wydajesz się, że pierwszy i ostatni wołasz bardziej jako „globalny” niż środek, co oczywiście nie jest.
TJ Crowder
4
środkowy uważa się za używany wewnątrz funkcji, wszystkie byłyby takie same, gdyby były użyte w głównym zakresie. moim założeniem było używanie var wewnątrz funkcji
Umair Jabbar
4
@Umair: „używanie var wewnątrz funkcji było moim założeniem” Ach, w porządku. Ale to nie jest pytanie. Pytanie bardzo wyraźnie brzmi „w zasięgu globalnym” . Jeśli zamierzasz zmienić to założenie (co jest dość sprawiedliwe, aby rozwinąć i wyjaśnić bardziej ogólny punkt), musisz wyjaśnić, że właśnie to robisz w swojej odpowiedzi.
TJ Crowder
10
<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

Czy istnieje obiekt globalny, z którego domyślnie są zawieszone wszystkie zmienary? np .: „deklaracja globals.noVar”

Cody
źródło
Bardzo fajna eksploracja. Zdecydowany przewodnik po użyciu window.*deklaracji. Ta deklaracja wygląda najbezpieczniej przed wklejeniem kodu, a także wyczyścić.
Dan.
7

Na podstawie doskonałej odpowiedzi TJ Crowder : ( Off-topic: Unikaj bałaganuwindow )

Oto przykład jego pomysłu:

HTML

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="init.js"></script>
    <script type="text/javascript">
      MYLIBRARY.init(["firstValue", 2, "thirdValue"]);
    </script>
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Hello !</h1>
  </body>    
</html>

init.js (na podstawie tej odpowiedzi )

var MYLIBRARY = MYLIBRARY || (function(){
    var _args = {}; // private

    return {
        init : function(Args) {
            _args = Args;
            // some other initialising
        },
        helloWorld : function(i) {
            return _args[i];
        }
    };
}());

script.js

// Here you can use the values defined in the html as if it were a global variable
var a = "Hello World " + MYLIBRARY.helloWorld(2);

alert(a);

Oto plnkr . Mam nadzieję, że to pomoże!

robe007
źródło
5

W zakresie globalnym nie ma różnicy semantycznej.

Ale naprawdę powinieneś unikać, a=0ponieważ ustawiasz wartość na niezadeklarowaną zmienną.

Użyj również zamknięć, aby w ogóle uniknąć edytowania zasięgu globalnego

(function() {
   // do stuff locally

   // Hoist something to global scope
   window.someGlobal = someLocal
}());

Zawsze używaj zamknięć i zawsze podnoś do globalnego zasięgu, gdy jest to absolutnie konieczne. Tak czy inaczej, przez większość komunikacji powinieneś używać asynchronicznej obsługi zdarzeń.

Jak wspomniano @AvianMoncellor, istnieje błąd IE, który var a = foodeklaruje jedynie globalny zasięg pliku. Jest to problem z notorycznie zepsutym tłumaczem IE. Ten błąd brzmi znajomo, więc prawdopodobnie jest to prawda.

Więc trzymaj się window.globalName = someLocalpointer

Raynos
źródło
2
„W zakresie globalnym nie ma różnicy semantycznej”. Faktycznie, istnieje ogromna różnica semantyczna, mechanizmy, zgodnie z którym nieruchomość zostanie zdefiniowane są zupełnie inne - ale w praktyce sprowadza się tylko do małej rzeczywistej różnicy (w które nie można ). deletevar
TJ Crowder
@ TJ Crowder Nie wiedziałem o tym. Myślałem, że deklaracja zmiennej ustawia właściwości obiektu zmiennego. Nie wiedziałem, że nie można ich usunąć.
Raynos
Tak. Są również zdefiniowane wcześniej, jeśli używasz var. Są to po prostu zupełnie inne mechanizmy, które mają bardzo podobny praktyczny wynik. :-)
TJ Crowder
@ TJ Crowder Zapomniałem wspomnieć, że varprzeskakuje do końca zakresu.
Raynos