Jonas: The next topic we need to talk about
is a very misunderstood concept in JavaScript
and that is hoisting.
So we learned that an execution context
always contains three parts.
A variable environment,
the scope chain in the current context,
and the disk keyword.
We already learned about the scope chain
and so now it's time to take a closer look
at the variable environment
and in particular at how variables
are actually created in JavaScript.
So in JavaScript
we have a mechanism called hoisting.
And hoisting basically make
some types of variables accessible,
or let's say usable in the code
before they are actually declared in the code.
Now, many people simply define hoisting
by saying that variables are magically lifted
or moved to the top of their scope
for example, to the top of a function.
And that is actually what hoisting
looks like on the surface.
But behind the scenes
that's in fact not what happens.
Instead, behind the scenes
the code is basically scanned
for variable declarations before it is executed.
So this happens during the so-called
creation phase of the execution context
that we talked about before.
Then for each variable that is found in the code,
a new property is created
in a variable environment object.
And that's how hoisting really works.
Now, hoisting does not work the same
for all variable types.
And so let's analyze the way hosting works
for function declarations,
variables defined with var
variables defined with let or const
and function expressions,
and also arrow functions.
So function declarations are actually hoisted
and the initial value in the variable environment
is set to the actual function.
So in practice, what this means
is that we can use function declarations
before they are actually declared in the code,
again, because they are stored
in the variable environment object,
even before the code starts executing.
And I actually told you before
that function declarations work this way,
but now you understand why.
It is because of hoisting.
And just to make this table a bit more complete
so that it serves as a nice overview for you,
I also show here that function declarations
are block scoped as we learned before.
Just keep in mind that this is only true
for strict mode.
So if you're using a sloppy mode,
which you shouldn't ,
then functions are functioned sculpt.
Next, variables declared with var
are also hoisted, but hoisting works
in a different way here.
So unlike functions,
when we try to access a var variable
before it's declared in a code,
we don't get the declared value
but we get undefined.
And this is a really weird behavior for beginners.
You might expect that you simply get an error
when using a variable before declaring it
or to get the actual value.
But not to get undefined
because getting undefined is just really weird
and it's not really useful either, right?
And actually this behavior
is a common source of bugs in JavaScript.
So this is one of the main reasons
why in modern JavaScript
we almost never use var.
Now on the other hand,
let and const variables are not hoisted.
I mean, technically they are actually hoisted
but their value is basically set to an initialized.
So there is no value to work with at all.
And so in practice, it is as if hoisting
was not happening at all.
Instead, we say that these variables
are placed in a so-called Temporal Dead Zone or TDZ
which makes it so that we can't access the variables
between the beginning of the scope
and to place where the variables are declared.
So as a consequence,
if we attempt to use a let or const variable
before it's declared, we get an error.
Also keep in mind that let and const are block scoped.
So they exist only in the block
in which they were created.
And all these factors together
is basically the reason why let and const
were first introduced into the language,
and why we use them now instead of var
in modern JavaScript.
Okay.
But now what about function expressions
and arrow functions?
How does hoisting work for this?
Well, it depends if they were created
using var or const or let.
Because keep in mind that these functions
are simply variables.
And so they behave the exact same way
as variables in regard to hoisting.
This means that a function expression
or arrow function created with var
is hoisted to undefined.
But if created with let or const,
it's not usable before it's declared in a code
because of the Temporal Dead Zone
so again, just like normal variables, right?
And this is actually the reason why I told you earlier
that we cannot use function expressions
before we write them in the code,
unlike function declarations.
Okay.
But now before finishing,
let's take a more detailed look
at this mysterious Temporal Dead Zone.
So in this example code
we're gonna look at the job variable.
It is a const so it's scoped only to this if block
and it's gonna be accessible
starting from the line where it's defined.
Why?
Well, because there is this Temporal Dead Zone
for the job variable.
It's basically the region of the scope
in which the variable is defined,
but can't be used in any way.
So it is as if the variable didn't even exist.
Now, if we still tried to access the variable
while in the TDZ like we actually
do in the first line of this if block,
then we get a reference error
telling us that we can't access job
before initialization.
So exactly as we learned
in the last slide, right?
However, if we tried to access a variable
that was actually never even created,
like in the last line here
where we want to log x,
then we get a different error message
saying that x is not defined at all.
What this means is that job is
in fact in the Temporal Dead Zone
where it is still initialized,
but the engine knows that it will
eventually be initialized
because it already read the code before
and set the job variable in the variable environment
to uninitialized.
Then when the execution reaches the line
where the variable is declared,
it is removed from the Temporal Dead Zone
and it's then safe to use.
So to recap, basically each and every
let and const variable get their own
Temporal Dead Zone that starts
at the beginning of the scope
until the line where it is defined.
And the variable is only safe to use
after the TDZ,
so the Temporal Dead Zone.
Alright, now what is actually the need
for JavaScript to have a Temporal Dead Zone?
Well, the main reason that the TDZ
was introduced in ES6 is that
the behavior I described before
makes it way easier to avoid and catch errors.
Because using a variable that is set to undefined
before it's actually declared
can cause serious bugs
which might be hard to find.
And I will show you a small example
in the next lecture.
So accessing variables before declaration
is bad practice and should be avoided.
And the best way to avoid it
is by simply getting an error
when we attempt to do so.
And that's exactly what a Temporal Dead Zone does.
A second and smaller reason why the TDZ exists
is to make const variables
actually work the way they are supposed to.
So as you know, we can't reassign const variables.
So it will not be possible
to set them to undefined first
and then assign their real value later.
Const should never be reassigned.
And so it's only assigned when execution
actually reaches the declaration.
And that makes it impossible
to use the variable before.
Okay? Makes sense?
Now, if hoisting creates so many problems,
why does it exist in the first place?
I get this question all the time.
And so let's quickly talk about that here.
So the creator of JavaScript basically
implemented hoisting
so that we can use function declarations
before we use them.
Because this is essential
for some programming techniques,
such as mutual recursion.
Some people also think that it makes code
a lot more readable.
Now, the fact that it also works for var declarations
is because that was the only way hoisting
could be implemented at the time.
So the hoisting of var variables
is basically just a byproduct
of hoisting functions.
And it probably seemed like a good idea
to simply set variables to undefined,
which in hindsight is not really that great.
But we need to remember that JavaScript
was never intended to become the huge
programming language that it is today.
Also, we can't remove this feature
from the language now.
And so we just use let and const to work around this.
Alright, great.
And with all that being said,
let's now work on some practical hoisting examples.