Hey, welcome to my series about destructuring one of those often shared snippet quizzes on Twitter. Welcome to this week's episode!
Snippet of the Week
This week's snippet is from Jonah Lawrence:
function fooBar() {
try {
console.log(foo);
} catch (error) {
console.log(error.name);
}
try {
console.log(bar);
} catch (error) {
console.log(error);
}
var foo = 'hello';
let bar = 'world';
}
fooBar();
In this snippet, they start with two try
/catch
blocks. These catch errors and allow us to act upon them, like adding an entry to our log-database or informing the user.
Both of them either print a variable or the name of the thrown object in case of error. Note that both of the trying-to-be-printed-variables are yet to be declared. That missing is the core trickery here.
After the two try
/catch
blocks, we have the actual declaration of the variables. The first one being initialized via var
, the second one with let
.
The Output
So, what will the output be if I run the given function? Surprisingly enough, it's undefined
and a ReferenceError
. To be a little more precise, we print the variable foo
(which is undefined
at this point), but not the variable bar
. Latter is recognized as not declared at all, hence ReferenceError
, which semantically means "You did not declare this variable".
Analysis
First things first, why is foo
undefined
? Shouldn't it be hello
? No, because of a concept called hoisting. Javascript engines move the (non-lexical) variable declarations to the top of the scope! What does this mean for our example? This shows how foo
gets processed:
function fooBar() {
var foo; // undefined
try {
console.log(foo);
} catch (error) {
console.log(error.name);
}
foo = 'hello';
}
An uninitialized variable is always just undefined
. The variable is declared; hence it can be printed but has not yet an assigned value.
The second and more significant question is why the behavior is not the same for let
and var
. Easy answer: let
is a lexical variable, while var
is not. ES6 introduced the difference for precisely this type of errors. The interpreter is more prone to detect hoisting mistakes that way.
A lexical variable behaves like most of us would intuitively expect it to be. One can not access it before it gets initialized. Such are placed in the Temporal Dead Zone (TDZ). Notably, lexical variables, so practically let
and const
, do not get hoisted.
One addition, one might instantly think that this snippet wants to trick you with scope differences. That's not the case here! The block-scope equals the function-scope.
Snippet Summary
- Trickery: Difference in the hoisting of lexical and non-lexical variables
- Key Learning: Lexical variables do not get hosted, thus can not be accessed before being initialized
-
Further reading:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_lexical_declaration_before_init
- https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz
- https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
Top comments (2)
Awesome explanation!
Thank you!