Zakres globalny i obiekt globalny

Gdy uruchamiamy kod JavaScript, dla takiego kodu zawsze powstaje zakres globalny, a także obiekt globalny. Obiekt ten w przypadku przeglądarki nazywa się window. Zarówno w zakresie globalnym, jak i w obiekcie globalnym mogą powstawać zmienne, które mają zasięg globalny. Zasięg globalny oznacza, że zmienne dostępne są w całym kodzie.

Zakres globalny

Czym jest w ogóle zakres globalny? Jeżeli zadeklarujemy zmienną albo klasę tak po prostu w pliku JavaScript to tworzymy ten kod w zakresie globalnym. Czyli kod umieszczony poza wszelkimi klamrami i innymi blokami kodu to zakres globalny.

let globalLet = 'My global let';
let globalConst = 'My global const';

class MyGlobalClass {
}
1
2
3
4
5

Do zakresu globalnego wpadają deklaracja z let oraz const, a także wszystkie klasy. Zakres globalny jest najwyższym zakresem w JavaScript i wszystkie inne zakresy dziedziczą z tego zakresu. Tak więc inne zakresy mają dostęp do wszystkiego, co jest w zakresie globalnym. Również do tych zmiennych mamy dostęp w innych plikach JavaScript.

To działanie opisuję przy zwykłym użyciu pliku JavaScript i wczytaniu go przez index.html. Działanie to jest inne, gdy zmienne te znajdą się w module. Również działanie to może być inne, gdy użyjemy narzędzia do budowania kodu jak webpack czy parcel.

Globalny obiekt window

Czym jest zatem obiekt globalny? Gdy uruchamiamy kolejne zakładki w przeglądarce, zawsze dla każdej zakładki powstaje oddzielny globalny obiekt window. Wystarczy uruchomić pustą zakładkę, otworzyć konsolę wpisać window, aby się przekonać, że taki obiekt już istnieje dla tej zakładki. Nawet jeżeli nie załadujemy do niej żadnego skryptu JavaScript.

Sam obiekt window zawiera w sobie mnóstwo właściwości, metod, które na co dzień używamy w naszym kodzie JavaScript:

document
location
history
console.log()
setTimeout()
setInterval()
alert()
1
2
3
4
5
6
7

Te właściwości i metody pochodzą z obiektu window. Tych właściwości, metod, handlerów i eventów jest bardzo wiele. Zachęcam do przejrzenia dokumentacji na stronie MDN.

Do tych właściwości możemy odwoływać się zarówno przez window jak i bezpośrednio.:

window.document;
document;
1
2

Używanie window jest opcjonalne i jest to tylko prefiks. Jeżeli nie używamy prefiksu window JavaScript i tak zaczyna poszukiwania w tym globalnym obiekcie.

Obiekt globalny jest dostępny z kontekstu globalnego. Jest więc dostępny z każdej części kodu. W przeglądarce obiekt ten nazywa się window, ale na innych środowiskach JavaScript może mieć inną nazwę, ale o tym za chwilę.

Globalne zmienne

Już wiemy, że tworząc zmienne za pomocą let i const umieszczamy je w kontekście globalnym:

const constVariable = 'My const';
let letVariable = 'My let';

console.log(constVariable); // 'My const'
console.log(letVariable); // 'My let'
1
2
3
4
5

Są więc one dostępne w całym kodzie.

Inaczej trochę się dzieje gdy tworzymy zmienną za pomocą var:

var varVariable = 'My var';

console.log(window.varVariable) // 'My var'
console.log(varVariable) // 'My var'
1
2
3
4

Zmienna var staje się zmienną obiektu globalnego, czyli w naszym przypadku window. Możemy się do niej odwołać przez window jak i bezpośrednio nie używając tego prefiksu.

Używając zmiennych var w naszym kodzie, modyfikujemy więc globalny obiekt. Dopisujemy do niego zmienne, a nawet możemy nadpisać zmienne, które w nim istnieją:

var alert = 'Achtung!';
1

Stworzyłem zmienna alert . Pole alert natywnie istnieje w obiekcie window, więc tak naprawdę nadpisałem funkcjonalność window.alert i od tej pory nie mogę w oknie przeglądarki wywołać okna dialogowego.

Jest to kolejny problem deklaracji zmiennych za pomocą var. Możemy przez przypadek nadpisać właściwość obiektu window. Jest to szczególnie niebezpieczne, gdy korzystamy z frameworków czy innych bibliotek, które czasami korzystają z globalnego obiektu. W ten sposób możemy wejść w łatwy konflikt nazw. Dlatego też należy unikać deklaracji zmiennych globalnych za pomocą var.

Globalne funkcje

Zobaczmy, jak zachowują się funkcje, które definiowane bezpośrednio w pliku stają się globalne:

function myFunction() {
  return 'Hello'
}

console.log(window.myFunction()); // 'Hello'
1
2
3
4
5

Jeżeli zadeklarujemy funkcję po prostu w pliku również mamy dostęp do niej z obiektu window. Działa to podobnie jak przy deklaracji var. Możemy funkcję taką wywołać zarówno z prefiksem window jak i bez.

Możemy jednak prostym sposobem uniknąć przypisania funkcji do obiektu window:

var fun1 = function() {
  return 'fun1'
}
let fun2 = function() {
  return 'fun2'
}
const fun3 = function() {
  return 'fun3'
}

console.log(window.fun1()) // 'fun1'
1
2
3
4
5
6
7
8
9
10
11

Gdy przypiszemy funkcję do zmiennej zadeklarowanej za pomocą let lub const to tak samo jak przy zwykłych deklaracjach zmiennych, funkcje te nie staną się częścią obiektu window, ale dalej będą w zakresie globalnym. Oczywiście, jeśli to będzie zmienna var to działanie jest takie samo jak przy zmiennych, zmienna var staje się częścią obiektu window.

Jeżeli nie korzystamy z modułów ES6 lub na przykład ze wzorca modułu to jest to dobry sposób na to, aby uniknąć dopisywania funkcji do obiektu globalnego window.

Niezadeklarowana zmienna

Trudno sobie wyobrazić, ale w języku JavaScript, może powstać zmienna, która nie jest zadeklarowana.

Zobaczmy taki kod:

function fun4() {
  foo = 'boo';
}

fun4();
console.log(window.foo); // 'boo'
console.log(foo); // 'boo'
1
2
3
4
5
6
7

Tworzę zmienną foo w funkcji. Nie używam ani var, ani let, ani const do jej deklaracji. Wywołuję funkcję aby mogła się wykonać i okazuje się, że mogę dostać się do tej zmiennej przez obiekt window. Zmienna ta zachowuje się podobnie jak zmienna var powstaje w globalnym obiekcie i jest dostępna globalnie.

Gdy kompilator trafi na taką niezadeklarowaną zmienną i nie może odnaleźć deklaracji w żadnym zakresie, to sam deklaruje taką zmienną. Mogłoby się wydawać, że jeżeli używamy niezadeklarowanej zmiennej w funkcji, to jest ona dostępna tylko w funkcji, ale tak nie jest. Stanie się ona częścią globalnego obiektu.

Na szczęście dzisiaj w swoim kodzie nie powinniście spotkać takich niezadeklarowanych zmiennych. Głównie dlatego, że wprowadzony w ES5 tryb ścisły dla JavaScript wyklucza taką możliwość. O trybie ścisłym będziemy jednak rozmawiać później w tym dziale.

Obiekt globalny i inne środowiska

Globalny obiekt window jest dostępny tylko w środowisku przeglądarki. Na przykład środowisko Node.js ma już inny globalny obiekt o nazwie global. Do obiektów globalnych w Web Workerach odwołujemy się przez self.

window
global
self
1
2
3

Gdybyśmy chcieli pisać uniwersalne skrypty, które działają i w Node.js i w przeglądarce, a także mogą być uruchamiane przez Web Workery, możemy mieć problem z dostępem do globalnego obiektu, bo w zależności od środowiska, obiekt ten nazywa się inaczej.

Dawniej powstawały skrypty, które pozwalały na dostęp do globalnego obiektu w zależności od środowiska:

function getGlobalObject() {
  if (typeof self !== 'undefined') {
    return self;
  }
  if (typeof window !== 'undefined') {
    return window;
  }
  if (typeof global !== 'undefined') {
    return global;
  }
  throw new Error('cannot find the global object');
};
const globalObj = getGlobalObject();
console.log(globalObj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Tutaj mamy funkcję, która sprawdza jaki globalny obiekt jest dostępy i w zależności od tego, zwraca ten obiekt. Wtedy dopiero możemy się tym obiektem posługiwać. Od razu powiem, że ta funkcja nie jest doskonała i w wielu przypadkach może nie zadziałać. Jest to tylko obraz tego, jak próbowano sobie radzić, gdy skrypty korzystały z globalnego obiektu i były uruchamiane na różnych środowiskach.

Dzisiaj dzięki ECMAScript 2020 do globalnego obiektu możemy dostać się jeszcze prościej:

console.log(globalThis);
1

Mamy przygotowaną specjalną właściwość o nazwie gloablThis, która zwraca globalny obiekt z danego środowiska uruchomieniowego. Tym prostym sposobem dostajemy globalny obiekt bez względu na środowisko.

Używać czy nie

Dowiedzieliśmy się sporo o zakresie globalnym i obiekcie globalnym window. Prawda jest taka, że w nowoczesnych aplikacjach, które przede wszystkim używają trybu ścisłego, czyli stric mode oraz używają modułów ES6, nie będziecie musieli przejmować problemami globalnego obiektu i globalnego zakresu.

Również każdy nowoczesny framework działa na podstawie modułu i uruchamia kod w trybie ścisłym. Jeżeli będziecie pisać kod w czystym JavaScript, należy unikać globalnego obiektu i nie przetrzymywać tam swoich zmiennych. Po prostu nie używamy deklaracji var.

Globalny obiekt może być używany przez inne biblioteki lub do pisania wyrafinowanego kodu. Przede wszystkim często jest używany przez skrypty polyfills. Używając obiektu window możemy nadpisać zmienne nie tylko obiektu window, ale innych bibliotek czy skryptów polyfills, które z tego obiektu korzystają bądź muszą skorzystać.

Pomimo tego, że problem zakresu globalnego i obiektu globalnego może Was nie dotknąć w nowoczesnych aplikacjach JavaScript, jest to fundament wiedzy o tym jak działa ten język i warto to wszystko wiedzieć.

Co warto zapamiętać:

  • JavaScript uruchamia swój kod w zakresie globalnym
  • w zakresie globalnym istnieje także obiekt globalny w przeglądarce jest to window
  • w zakresie globalnym powstają zmienne zadeklarowane za pomocą let i const, a także klasy
  • w obiekcie globalnym window powstają zmienne zadeklarowane za pomocą var oraz funkcje
  • najlepiej nie używać var i nie deklarować zmiennych w obiekcie window
  • dzisiaj nowoczesne aplikacje korzystają z modułów ES6, znika więc problem trudno kontrolowanych zmiennych globalnych