Let's continue our journey
of diving really deep into how JavaScript actually works
behind the scenes.
I really hope you have enjoyed it so far
and are able to see the immense value
that these lectures bring to the table.
Now, after this lecture,
there will finally be another coding lecture.
So hang tight, we're almost there.
But now let's get started with this lecture.
So we learned in the last lecture
that each execution context has a variable environment,
a scope chain and a this keyword.
So in this lecture, let's learn what scope
and a scope chain are,
why they are so important and how they work.
And let's start by understanding
what scoping actually means,
and learn about some related concepts as well.
So scoping controls how our program's variables
are organized and accessed by the JavaScript engine.
So basically scoping asks the question,
where do variables live?
Or where can we access a certain variable and where not?
Now in JavaScript,
we have something called lexical scoping.
And lexical scoping means that the way variables
are organized and accessed
is entirely controlled by the placement of functions
and of blocks in the programs code.
For example, a function that is written inside
another function has access to the variables
of the parent function, okay?
So again, variable scoping is influenced
by where exactly we write our functions and code blocks.
Okay, and now about scope itself.
Scope is the space or environment
in which a certain variable is declared,
simple as that.
And in the case of functions,
that's essentially the variable environment
which is stored in the functions execution context.
So if now you're asking yourself,
what's the difference between scope
and variable environment?
Then the answer is that for the case of functions,
it's basically the same.
Now in JavaScript, we have the global scope,
function scope, and block scope.
And we will talk about these in a second.
But first, let's also define what the scope
of a variable is.
So the scope of a variable is basically the entire region
of our code, where a certain variable can be accessed.
Now, some people use the word scope for all of this,
but I like to define all these concepts that we have here
in a clear way, because actually subtle differences.
For example, if you take a close look at it,
scope is not the same as scope of a variable.
And so you should know about the subtle differences, right?
And I know it might still sound all the same for now,
but after looking at a couple of examples
and writing some real code,
you will understand everything I just showed you here.
Anyway, let's now talk about the three different types
of scope in JavaScript.
So that's global scope, function scope and block scope.
And remember, scope is the place in our code
where variables are declared.
And when I say variables, the exact same thing
is true for functions as well.
Because in the end, functions are just values
that are stored in variables.
So first, the global scope is once more
for top level code.
So this is for variables that are declared
outside of any function or block.
These variables will be accessible everywhere
in our program, in all functions and all blocks.
So really, everywhere.
Next, each and every function creates a scope.
And the variables declared inside that function scope
are only accessible inside that function.
This is also called a local scope
opposed to the global scope.
So local variables live in the function so to say.
And outside of the function,
the variables are then not accessible at all.
Again, this is technically the same
as the functions variable environment,
but we still need to give it the name of scope
in this context,
because blocks also creates scopes.
Anyway, in this example here,
the now variable is 2037 inside the cog H function.
And therefore, we can use it in the function
to do calculations.
But outside of the function,
as we try to log it to the console,
we get a reference error.
So JavaScript is trying to find the now variable
in this global scope,
so outside of the function, but it cannot find it.
And so there is gonna be an error.
And if you remember, or pick game project
from the previous section,
there is also the reason why we had to declare
a couple of variables outside of the init function,
remember that?
So we had some variables declared in the init function,
and then that gave us an error
because other functions were trying
to access these variables.
But of course they were in the function scope.
And so they were locally scoped,
and so we couldn't access them
outside of that function where they were declared.
And here, it actually does not matter
what kind of function we're using.
So function declarations, function expressions
and arrow functions all create their own scope.
Now traditionally, only functions used to create scopes
in JavaScript.
But starting in ES6, blocks also creates scopes now.
And with blocks,
we mean everything that is between curly braces,
such as the block of an if statement or a for loop.
So just like with functions,
variables declared inside a block
are only accessible inside that block
and not outside of it.
Now, the big difference is that block scopes
only apply to variables declared with let or const, okay?
So again, only let and const variables
are restricted to the block in which they were created.
That's why we say that let and const variables
are block scoped.
So if I declared a variable using var in this block,
then that variable would actually still be accessible
outside of the block,
and would be scoped to the current function
or to the global scope.
And so we say that var is function scoped.
So in ES5 and before,
we only had global scope and function scope.
And that's why ES5 variables declared with var,
only care about functions, but not about blocks.
They simply ignore them.
Finally, also starting in ES6,
all functions are now also block scoped,
at least in strict mode,
which you should always be using anyway.
And just like with let and const variables,
this means that functions declared inside a block
are only accessible inside that block, okay?
And we will see examples of all that in the next video,
when we're gonna go back to coding.
So to recap, let and const variables
as well as functions are block scoped.
And if you already know other programming languages,
block scoping is probably more in line
with what you already know.
Function scopes are weird for some beginners
in the JavaScript world.
And that's why block scopes were introduced in ES6.
But now to understand all this a little bit better,
let's actually look at a more real and detailed example
and also learn about the scope chain.
And here we have some code with different functions
and blocks,
and we're gonna take a look at the scopes
that are in this code as well as build the scope chain.
And of course, we start with the global scope.
As you can see, the myName variable
is the only variable declaration
that we have in the global scope.
Now, technically, the first function
also counts as a variable
that is present in the global scope,
but I want to keep it simple here.
And so I will only consider variable declarations
and no functions, all right?
Just keep in mind that whatever I'm explaining here
for variables also works the same for functions.
Anyway, inside the global scope,
we have a scope for the first function
because each function creates its own scope, remember?
And what's in the scope?
Well, it's to age variable that's declared
right at the top of the function.
Next inside the first scope,
let's now consider the second function,
which will also create its own scope
containing the job variable set to teacher.
So as you see,
we have a nested structure of scopes
with one scope inside the other.
But now comes the actually interesting part.
Because here in the second function,
we have this line of code
where we need the myName variable
and the age variable,
which were both not declared inside the current scope.
But we really need these variables here,
because otherwise we can't create this string here, right?
So how can this be fixed?
How will the JavaScript engine know the values
of these variables?
Well, the secret is that every scope
always has access to all the variables
from all its outer scopes.
So from all its parent scopes.
In our example, this means that the second scope
can access the age variable from the scope
of the first function.
Of course, this also means that the first scope
can access variables that are in the global scope,
because that is the parent scope.
As a consequence of this,
the second scope will then also be able to access
the myName variable from the global scope,
because it has access to the variables
from the first scope.
And by the way, all this also applies to function arguments.
But in this example, we just don't have any.
And this is essentially how the scope chain works.
In other words,
if one scope needs to use a certain variable,
but cannot find it in the current scope,
it will look up in the scope chain
and see if it can find a variable
in one of the parent scopes.
If it can, it will then use that variable.
And if it can't, then there will be an error.
And this process is called variable lookup.
Now it's important to note that these variables
are not copied from one scope to another, okay?
Instead, scopes simply look up in the scope chain
until they find a variable that they need
and then they use it.
What's also extremely important to note
is that this does not work the other way around.
A certain scope will never, ever have access
to the variables of an inner scope.
In this example, the first scope, for example,
will never get access to the job variable
that is stored in the second scope, okay?
So again, one scope can only look up in a scope chain,
but it cannot look down basically.
So only parent scope can be used,
but no child scopes.
Anyway, with all this in place now,
this line of code can be executed
and print to the console.
Jonas is a 30 year old teacher,
even though the myName and age variables
were not defined in the current scope.
All the engine did was to get them from the scope chain.
And as you might be noticing,
we have actually already done this before in our own code.
We just didn't really understand what was going on
and how it all worked.
But now we do know how it works.
Amazing, right?
Anyway, we still have one more scope left here,
and that's the one created by this block here.
Remember that starting with ES6,
not only functions create scopes, but also blocks.
However, these scopes only work for the ES6 variable types.
So for let and const variables.
That's why the only variable that's in the scope
is the decade variable.
The millennial variable isn't declared with const or let,
and therefore it is not scoped to just this block.
Instead, the millennial variable is actually part
of the first function scope.
So again, for a variable declared with var,
block scopes don't apply at all.
They are functions scoped, not block scoped.
Let and const on the other hand
are in fact blocks scoped, okay?
This is one of the fundamental things
that you need to keep in mind about let, const and var,
and about scoping in general.
So if you're taking notes and I hope you are taking
lots of notes,
then this must definitely be in there.
Now about a scope chain,
if the millennial variable is in the first function scope,
then of course the second function scope
also has access to it,
even if it doesn't really need that variable.
Also the scope chain does of course,
apply to block scopes as well.
And therefore in or if block scope,
we get access to all the variables
from all its outer scopes.
So from the first function scope,
and of course from the global scope.
That's why I said in the last slide
that variables in a global scope
are accessible from everywhere.
They are, because they are always
at the top of the scope chain.
In fact, we call variables in the global scope,
global variables, very creative, right?
But we actually use this term a lot in JavaScript.
Now it's important to understand
that our purple blocks scope
does not get access to any variables
from the yellow second function scope.
And the same, the other way around.
And why is that?
Well it's because of lexical scoping
as we learned in the last slide.
So the way that we can access variables
depends on where the scope is placed,
so where it is written in the code.
In this case, none of these two scopes is written
inside of one another.
They're both child scopes of the first function.
We could even say that they are a sibling scopes.
And so by the rules of lexical scoping,
they cannot have access to each others variables,
simply because one is not written inside the other one.
We can also say that the scope chain
only works upwards, not sideways.
Okay, so this was a lot to take in,
but I hope that everything's still keeps making sense
at this point.
And if not, don't worry,
we will see all this working in practice
in the next video.
But for now, though,
there is one more thing that we need to talk about,
which is the difference between the scope chain
and to call stack.
I get a lot of questions about this all the time.
And so I decided to talk about
how the call stack, execution context,
variable environments and scope
are all related to one another.
So before we move on to the next video.
And once more,
let's look at some more code here.
So we have three functions called first, second and third,
in order to make this easier to understand.
We start by calling the first function,
which then calls the second function,
which in turn calls the third function.
So from what we learned before,
the call stack for this example will look like this, right?
One execution context for each function
in the exact order in which they were called.
They also included the variable environment
of each execution context.
For now, all this has nothing to do with scopes
or the scope chain, all right?
All I'm doing is creating one execution context
for each function call and filling it
with the variables of that function.
And you can pause the video here for a moment
to understand the content of each variable environment.
Okay, and now that you did that
and we have all these variable environments in place,
we can actually start building the scope chain.
As always, we're gonna start with the global scope.
And the variables available in the global scope
are exactly the ones stored in the variable environment
of the global execution context.
And given everything we've learned so far,
that makes sense, right?
And note that in this example,
I am actually including functions in each scope
unlike we did in the previous slide.
Now in the global scope, we also call the first function,
which is the reason why we have an execution context for it
in the call stack.
And this function of course, also gets its own scope,
which contains all the variables that are declared
inside of the function.
And once again, this is exactly the same
as the variable environment
of the functions execution context.
However, that's not all
because now we already know about the scope chain.
So the first scope also gets access to all the variables
from its parent scope,
thanks to the scope chain.
Now, as we already know,
the scope chain is all about the order
in which functions are written in the code.
But what's really important to note here
is that the scope chain has nothing to do with the order
in which functions were called.
Or in other words, the scope chain has nothing to do
with the order of the execution contexts in the call stack.
The scope chain does get the variable environments
from the execution context as shown by the red arrows here,
but that's it.
The order of function calls is not relevant
to the scope chain at all, all right?
Really keep that in mind.
Now, moving on to the second function now,
once again, its scope is equal to its variable environment.
Also it's lexically written within the first function.
And so of course,
it will have access to all its parent scopes as well.
So we can say that the scope chain in a certain scope
is equal to adding together all the variable environments
of all the parent scopes.
And so this is our scope,
and the scope chain are built in the JavaScript engine
behind the scenes.
Okay.
Now in the second function,
we try to call the third function.
But why does that work?
Well, it works because the third function
is in the scope chain of the second function scope
as we can see here in our scope chain diagram.
It's a function in the global scope or a global function,
and therefore it's accessible everywhere.
Of course, this will create a new scope
along with the scope chain as we already know.
Great, so what happens in this third function?
Well, we're trying to act as variables B, C,
D and A here.
D is no problem because it's right there
in the third function scope.
So that one is easy.
Then variable C is not in a local scope
and so JavaScript needs to do a variable lookup.
So it looks up in a scope chain
looking for variable C,
but it's not there.
And of course it isn't,
because C is defined in the second function,
and there is just no way in which the third function
can access variables defined in the second function.
And that is true,
even though it was the second function who called the third.
And so here is even more proof
that the order in which functions are called
does not affect the scope chain at all.
And so here as a result,
we get the reference error
because both C and B cannot be found
in the third scope nor in the scope chain.
Okay.
And with this, I hope I made it crystal clear
that execution context, variable environments,
the call stack scope and the scope chain are all different,
but still very related concepts.
And if it's not yet crystal clear,
then it's no problem to maybe rewatch this slide,
or maybe even this entire lecture a little bit later.
And I know that this was quite a long lecture
with a ton of stuff to take in.
And so here is a handy summary
with the main takeaways from this video.
So to start, scoping asks the question,
"Where do variables live?"
Or "Where can we access a certain variable,
"and where not?"
That's what scoping is all about.
Now, there are three types scope in JavaScript.
The global scope, scopes defined by functions
and scopes defined by blocks, starting in ES6.
However, only let and const variables are block scoped.
Variables declared with var automatically end up
in the closest function scope.
Next in JavaScript, we have lexical scoping,
which means that the rules of where we can access variables
are based on where in the code functions
and blocks are written.
And now, let the magic begin,
because every scope always has access
to all the variables from all it's outer scopes.
And this is what we call the scope chain.
When a certain variable is not in the current scope,
the engine looks up in the scope chain
until it finds the variable that it's looking for,
and this process is called variable lookup.
It's important to note that the scope chain
is a one way street.
So a scope will never ever have access to the variables
of an inner scope, only of outer scopes.
We can also think of the scope chain in a certain scope
as being equal to adding together
all the variable environments of all the parent scopes.
And finally, we need to keep in mind
that the scope chain has nothing to do
with the order in which functions were called.
So the order of function calls
does not affect the scope chain at all.
Okay, and now that's actually it.
This is in a nutshell, scoping in JavaScript.
See you in the next video.