W sumie fajna ciekawostka.
Wydaje mi się, że kluczem do zrozumienia tego zachowania jest działanie let
/const
- poza innymi zasadami co do scope'a mają one jeszcze jedną własciwość - przy zadeklarowaniu w globalnej przestrzeni nie wchodzą one w skład obiektu window
(lub global
w Node.js), ale niejako przesłaniają go, przykład:
var foo = 1
const bar = 2
console.log(foo) // => 1
console.log(window.foo) // => 1
console.log(bar) // => 2
console.log(window.bar) // => undefined
Poza tym do const
nie jest nigdy niejawnie przypisywana wartość undefined
- zawsze trzeba do niej ręcznie przypisać wartość równocześnie z deklaracją (wiadomo, to istota consta):
let foo // => undefined
const bar // Uncaught SyntaxError: Missing initializer in const declaration
Dodatkowo przy równoczesnej deklaracji i przypisaniu do let
i const
nie jest przypisywana najpierw wartość undefined
:
let foo = 1 // => 1 nie undefined -> 1
const bar = 1 // => 1 nie undefined -> 1
Co się dzieje w konsoli w Twoim przypadku?
> const foo = (() => { throw new Error() })() // => Uncaught Error...
- następuje zadeklarowanie zmiennej
foo
przesłaniającej window.foo
- następuje próba przypisania jej wartości, która kończy się niepowodzeniem (rzucenie wyjątku) - żadna wartość (undefined to też wartość) nie zostaje przypisana - zmienna trafia w swoiste "limbo" - identyfikator jest zarezerwowany, wartości brak.
Teraz próbujesz wywołać typeof:
> typeof foo // Uncaught ReferenceError...
Interpreter najpierw ewaluuje zmienną foo
- nie ma ona wartości (choć identyfikator istnieje, więc nie weźmie wartości z obiektu window
), więc wywala błąd (w przypadku var
będzie typ będzie "undefined"
, bo sięgnie po wartość z window
).
Specyfikacja języka z tego co wiem nie definiuje jak ma być zaimplementowany REPL, więc leży to w gestii twórcy przeglądarki - ot zrobili to tak a nie inaczej (mogliby też cofnąć deklarację zmiennej przy błędzie, ale nie wiem czy to lepsze rozwiązanie).
W każdym razie:
co rujnuje moją "wiedzę", że typeof jest zawsze "bezpieczne"
nie jest prawdziwe, bo imo nie da się doprowadzić do takiej sytuacji poza REPL - po rzuceniu wyjątku program się kończy lub wskakuje do bloku catch
(o ile jest), więc typeof na takiej felernej zmiennej zadeklarowanej przez const
nie da się wykonać (ze względu na scope i brak możliwości redeklaracji), a dla let
będzie domyślna wartość undefined
:
let foo // zmienna jest deklarowana i niejawnie jest jej przypisywana wartość undefined
try {
foo = (() => { throw new Error() })() // nowa wartość nie zostaje przypisana, linia rzuca wyjątkiem
console.log(typeof foo) // ten kod się nie wykona
} catch (e) {
console.log(typeof foo) // undefined - zgodnie z ostatnim (niejawnym) przypisaniem
}
Mam nadzieję, że nie zagmatwałem jeszcze bardziej :D
Edit:
Jeśli chodzi o tyopeof
w samym IFFE to zachodzi tam sytuacja analogiczna - identyfikator jest zarezerwowany, ale wartości nie ma jeszcze - nie może sięgnąć do obiektu window
w tamtym momencie bo wyższy priorytet ma const
. No ale to też nie wpływa za bardzo na bezpieczeństwo użycia typeof
, bo wywala to program od razu (zresztą linter to wyłapie bez problemu).