Każdy junior front-end developer powinien wiedzieć, czym jest hoisting. Zagadnienie to można dosyć łatwo zrozumieć, więc jeśli ktoś jeszcze o nim nie słyszał, może szybko nadrobić zaległości:
Co to jest hoisting?
Hoisting polega na wynoszeniu deklaracji zmiennych i funkcji „na górę” kodu (konkretnie na początek funkcji lub do zakresu zmiennych globalnych, w zależności od tego, gdzie dana zmienna lub funkcja została zadeklarowana). Oznacza to, że można odwołać się do zmiennej lub funkcji zanim jeszcze zostanie zadeklarowana.
Zobaczymy to najpierw na przykładzie zmiennnych:
Wynoszenie zmiennych
ES5 – var
Trzeba rozróżnić tu dwie rzeczy – deklarację i inicjalizację zmiennej. Deklaracja to zdefiniowanie zmiennej, a inicjalizacja to przypisanie jej wartości. Można zrobić te dwie rzeczy „jednorazowo” lub osobno:
function example() {
var foo; // deklaracja zmiennej
foo = "nowa wartosc zmiennej foo"; // inicjalizacja zmiennej
var boo = "od razu przypisujemy wartosc" // deklaracja i inicjalizacja
}
Pamiętać trzeba, że wynoszona jest tylko deklaracja zmiennej. Zatem, jeśli odwołamy się do zmniennej przed jej zdefiniowaniem, w odpowiedzi otrzymamy 'undefined’:
function example() {
console.log(foo); // undefined
var foo = 10;
console.log(foo); // 10
}
Dzieje się tak dlatego, że zmienna jest zadeklarowana, ale nie jest zdefiniowana jej wartość. Wartość odczytać możemy dopiero po inicjalizacji zmiennej, czyli poniżej var foo = 10;
ES6 – let i const
W przypadku użycia let i const zmienne nie są wynoszone na górę zakresu funkcji, ani do środowiska globalnego. Ogranicza to błędy, jakie mogłyby wyniknąć z nadpisania nazw zmiennych.
Miejsce pomiędzy deklaracją zmiennej a początkiem jej zakresu nazywa się Temporal Dead Zone (TDZ). Oznacza to, że jeśli odwołamy się do zmiennej przed jej zadeklarowaniem, otrzymamy error: ReferenceError: Cannot access 'foo’ before initialization.
function example() {
// Temporal Dead Zone
console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
let foo = 10;
console.log(foo); // 10
}
Zmienną możemy odczytać dopiero po jej zadeklarowaniu.
Wynoszenie funkcji
Function declaration // Deklaracja funkcji
Hoisting dotyczy również funkcji, zapisanej przy użyciu deklaracji funkcji. Tak jak przy zmiennych, oznacza to, że możemy odwołać się do funkcji zanim zostanie ona zadeklarowana:
foo();
function foo() {
console.log('It works!'); // It works!
}
Function expression // Wyrażenie funkcji
W przypadku wyrażeń funkcji zasady dotyczące hoistinu są podobne jak w w przypadku zmiennych, tzn, jeśli przypiszemy funkcję do zmiennej var, deklaracja funkcji zostanie wyniesiona na górę zakresu zmiennej, ale ponieważ wynoszona jest tylko deklaracja, i tak nie użyjemy funkcji przed zadeklarowaniem:
foo(); // TypeError: foo is not a function
var foo = function() {
console.log('It works!');
}
W przypadku let i const natomiast tworzy się martwa strefa (TDZ) i chcąc odwołać się do funkcji przed inicjalizacją otrzymujemy error:
foo(); // ReferenceError: Cannot access 'foo' before initialization
let foo = function() {
console.log('It works!');
}
Z hoistingiem trzeba uważać, może się bowiem zdarzyć, że niechący nadpiszemy jakąś zmienną, do której myślimy, że nie mamy dostępu, bo przecież została zadeklarowana dopiero kilka linijek kodu niżej. Z tego względu w większości przypadków bezpieczniej jest używać let i const zamiast var.