TDZ
Piekło zamarzło! Przyszedł dzień, w którym Comandeer posypuje głowę popiołem i przyznaje się do błędu.
Mea culpa
W swojej książce, JavaScript. Programowanie zaawansowane, w rozdziale 2., na stronie 32 stwierdzam:
Jak zatem widać, nowa metoda deklaracji zmiennych działa w sposób, o który od wieków upominali się programiści JS: zmienne ograniczone zostały do bloków, w których je zadeklarowano. Co więcej, żeby już całkowicie “unormalnić” ten obszar JS-a, zrezygnowano z hoistingu (wynoszenia).
Ten fragment (a zwłaszcza drugie zdanie) jest niepoprawny i wynika to ze zbytniego uproszczenia, jakiego się dopuściłem.
Temporal Dead Zone, czyli hoisting bez hoistingu
Doskonale wiadomo, że zmienne deklarowane przy pomocy var
są hoistowane (wynoszone). Rozpatrzmy taki, klasyczny, przykład:
( function() {
console.log( typeof a ); // undefined
var a = 1;
}() );
Jak widać, można pobrać typ zmiennej przed jej deklaracją. Umożliwia to właśnie mechanizm hoistingu, który wszystkie deklaracje zmiennych “wynosi” na sam początek danego scope, zostawiając na miejscu jedynie przypisanie wartości do zmiennej. Powyższy przykład jest zatem widziany przez parser JS mniej więcej tak:
( function() {
var a;
console.log( typeof a ); // undefined
a = 1;
}() );
Gdy zamienimy var
na let
zauważymy, że zachowanie skryptu się znacząco zmienia:
( function() {
console.log( typeof a ); // Uncaught ReferenceError: a is not defined
let a = 1;
}() );
Wniosek, który się nasuwa, jest oczywisty: zmienne deklarowane przez let
nie są hoistowane. I gdybyśmy poprzestali na tego typu przykładzie, faktycznie można by tak przyjąć. Spójrzmy jednak na ciut inną sytuację:
( function() {
var a = 1;
( function() {
console.log( typeof a ); // undefined
var a = 'hublabubla';
}() );
}() );
W tym przykładzie po raz kolejny pojawia się undefined
zamiast number
. Dlaczego? Bo zmienne są hoistowane na górę najbliższego scope. W tym wypadku wewnętrzna funkcja stanowi osobny scope, stąd przesłania liczbową zmienną z zewnętrznego scope.
Jeśli założymy, że zmienne deklarowane przy pomocy let
faktycznie nie są hoistowane, to w przykładzie z let
powinniśmy uzyskać number
(bo deklaracja let
jest dopiero po console.log
, zatem do tego czasu powinna być dostępna zmienna z wyższego scope). Sprawdźmy:
( function() {
let a = 1;
( function() {
console.log( typeof a ); // Uncaught ReferenceError: a is not defined
let a = 'hublabubla';
}() );
}() );
Co tu się stało? Ano, nadzialiśmy się na tzw. Temporal Dead Zone (Czasowo Martwa Strefa). Choć nazwa brzmi strasznie, sam mechanizm aż tak straszny nie jest. Składa się on tak naprawdę z dwóch elementów:
- Zmienne deklarowane przy pomocy
let
(ale takżeconst
) są hoistowane na górę najbliższego scope. - Od początku scope aż do miejsca faktycznej deklaracji zmiennej istnieje TDZ, uniemożliwiając dostęp do zmiennej.
Mechanizm ten wprowadzono dla const
, aby uniemożliwić nadpisanie stałej wewnątrz danego scope i przeniesiono następnie to zachowanie także dla let
– dla zachowania spójności.
Po więcej informacji o TDZ odsyłam – jak zawsze – do odpowiedniego fragmentu twórczości Rauschmayera.
Mea maxima culpa
Jak widać, TDZ w większości przypadków zachowuje się tak, jakby zmienne let
i const
nie były hoistowane. Stąd od biedy fragment w mojej książce mógłby brzmieć:
Co więcej, żeby już całkowicie “unormalnić” ten obszar JS-a, zmienne te w przeważającej większości przypadków zachowują się, jakby nie podlegały hoistingowi [tutaj przypis wspominający o TDZ i odsyłający do Exploring JS].
Posypuję głowę popiołem i przyznaję: pominięcie opisu TDZ było sporym przeoczeniem z mojej strony. Jedyne, co mogę zrobić na swoje usprawiedliwienie, to zacytować Konrada:
Język kłamie głosowi, a głos myślom kłamie;
Komentarze
Przejdź do komentarzy bezpośrednio na Githubie.
Dawne komentarze
Ten blog wcześniej korzystał z systemu komentarzy Disqus. Jednakże pożegnaliśmy się i postanowiłem, że zaimportuję do nowej wersji stare komentarze z niego. Cóż, jego system eksportu na wiele nie pozwala…
Wydaje mi się, że nie do końca trzeba to traktować jako błąd, może jedynie jako nieco większe uogolnienie. Celem książki jest stworzenie konretnego narzędzia, a nie omawianie wszystkich podstaw JS bo wtedy musiałaby być 5 razy grubsza :) a poza tym to takie błędne lub mylące informacje są na pewno mniej problematyczne dla początkujących niż błędy w listingach co w wielu książkach się zdarza. A tak na marginesie to kiedy się wezmiesz za książkę o dostępności www? Jak Ty tego nie zrobisz to chyba już nikt nie zrobi :)
Mimo wszystko osobiście uważam to za zbyt duże uogólnienie, a tym samym – błąd. Zwłaszcza, że od samego początku nie do końca chodziło mi o to, co ostatecznie pojawiło się w tekście (chciałem wyjaśnić TDZ bez niepotrzebnego opisywania całego mechanizmu, co wyszło mi po prostu fatalnie).
Co do książki o dostępności WWW: cóż, nie mówię nie :P
Znający wartość całości opracowania uznają to niedoprecyzowanie za drobiazg.
Dziękuję przy okazji za inspirującą, godną polecenia "pozycję"!