Przewiń do treści 

TDZ

Opublikowany:
Autor:
Comandeer
Kategorie:
JavaScript

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że const) 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…

  1. Opublikowany:
    Autor:
    disqus_Sle3kiCPGK

    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 :)

    1. Opublikowany:
      Autor:
      Comandeer

      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

  2. Opublikowany:
    Autor:
    piotr_m_dry

    Znający wartość całości opracowania uznają to niedoprecyzowanie za drobiazg.
    Dziękuję przy okazji za inspirującą, godną polecenia "pozycję"!