Objective: By the end of this checkpoint, you will have better understanding of ‘variable scopes’.
By variable scope, we are talking about the visibility of variables in our code; or in other words, which parts of the code can access and modify a variable.
JavaScript has been going through some big changes over the last few years; these include the addition of a couple of interesting new keywords for declaring variables and deal with scope in new ways to what has come before. let
and const
were added as part of ES6 (a.k.a. ES2015) which has been around for about half a decade now; browser support is good and for everything else there’s Babel for transpiling ES6 into a more widely adopted JavaScript. So now is a good opportunity to look again at how we declare variables and gain some understanding of how they scope.
In this checkpoint, we are going to run through a number of JavaScript examples to show how global, local, and block scopes work. We will also look at let
and const
statements for anyone who is not already familiar with these.
Let’s begin by considering global scope. Globally declared variables can be accessed and modified anywhere in the code (well almost, but we will come to the exceptions later).
Global variables are declared in the code outside of any function definitions, in the top-level scope.
In this example, a
is a global variable; it is therefore readily available in any function within our code. So here we can output the value of a
from the method alpha
. When we call alpha
, the value Fred Flinstone
is written to the console.
When declaring global variables in a web browser, they are also properties of the global window
object. Take a look at this example:
b
can be accessed/modified as the property of the window
object window.b
. Of course it isn’t necessary to assign the new value to b
via the window
object, this is just to prove a point. We are more likely to write the above as:
Be careful when using global variables. They can lead to unreadable code that is also difficult to test. We’ve seen many developers become stuck with global variables trying to discover where the variable’s value is being changed within our codebase causing unexpected bugs. It is far better to pass variables as arguments to functions than rely on globals. For those reasons, global variables should be used sparingly, if at all.
If you really need to use the global scope it is a good idea to namespace your variables so that they become properties of a global object. For example, create a global object named something like globals or app.
If you are using NodeJS then the top-level scope is not the same as the global scope. If you use var foobar
in a NodeJS module it is local to that module. To define a global variable in NodeJS we need to use the global namespace object, global
.
It’s important to be aware that if you do not declare a variable using one of the keywords var
, let
, or const
in your codebase, then the variable is given a global scope.
It’s a good idea to always initially declare variables with one of the variable keywords (particularly with let
and const
, more on that below). This way we are in control of each variable’s scope within our code.
Now we come to the local scope.
Variables declared within a function are scoped locally to that function. In the examples above, both variables b
and c
are local to their respective methods. b
is locally scoped to the function delta
as it is being declared as an argument of the method, only the value of a
is being passed to the function not the variable itself; c
is declared as a local variable of epsilon
inside the function. However, what if we have the following code? what does this write to the console?
The answer is 'Jerry'
although at first this may not be obvious. This is one of those nasty job interview questions you might get asked if being grilled over your JavaScript abilities. Inside the function zeta
, we are declaring a new variable d
that has a local scope. When using var
to declare variables, JavaScript initiates the variable at the top of the current scope regardless of where it has been included in the code. So the fact that we have declared d
locally within the conditional is irrelevant. Essentially JavaScript is reinterpreting this code as:
This is known as Hoisting. It’s a feature of JavaScript that is well worth being aware of as it can easily create bugs in your code if variables aren’t initiated at the top of a scope. Thankfully we now have let
and const
that will help avoid these issues going forward. So let’s take a look at how we can use let
as block scope variables.
With the arrival of ES6 a few years ago came two new keywords for declaring variables: let
and const
. Both keywords allow us to scope to a block of code, that’s anything between two curly braces, {}
.
let
is seen by many as a replacement to the existing var
. However, this isn’t strictly true as they scope variables differently. Whereas a var
statement allows us to create a locally scoped variable, let
scopes a variable to the block. Of course a block can be a function allowing us to use let
pretty much as we would have used var
previously.
Here a
is block scoped to the function eta
. We can also scope to conditional blocks and loops. The block scope includes any sub-blocks contained within the top-level block that the variable is defined.
In this example, b
is block scoped to the for
loop (which includes the conditional block inside it). So it will output the odd numbers 1
and 3
and then throw an error because we can’t access b
outside the loop that it was scoped to.
What about our strange little function zeta
from earlier where we saw JavaScript’s bizarre hoisting effect? If we rewrite the function to use let
what happens?
This time zeta
outputs 'Tom'
because d
is block scoped to the conditional, but does this mean hoisting is not happening here? No, when we use either let
or const
, JavaScript will still hoist the variables to the top of the scope. Whereas with var
, the hoisted variables are intiated with undefined
. let
and const
variables remain uninitiated. They exist in a temporal dead zone.
Let’s take a look what happens when we attempt to access a block scoped variable before it is initiated.
So calling theta
will output undefined
for the locally scoped variable e
and throw an error for the block scoped variable f
. We cannot use f
until after it is initiated, in this case where we set its value as ‘Road Runner’.
There is one other important difference between let
and var
that we need to mention before moving on. When we use var
in the very top level of our code, it becomes a global variable and is added to the window
object in browsers. With let
, whilst the variable will become global in the sense that it is block scoped to the entire codebase, it does not become a property of the window
object.
We previously mentioned const
in passing. This keyword was introduced alongside let
as part of ES6. In terms of scope, it works the same as let
.
In this example, a
is scoped to the if
statement so that it can be accessed inside the conditional, but is undefined
outside of it.
Unlike let
, variables defined by const
cannot be changed through re-assignment.
However, when working with arrays or objects, things are a little different. We still cannot re-assign, so the following will fail:
But we can modify a const
array or object unless we use Object.freeze()
on the variable to make it immutable.
We kind of saw this earlier when looking at hoisting. But let’s take a look at what happens when we redeclare a variable in the local scope that already exists globally.
When we redeclare a global variable in the local scope, JavaScript initiates a new local variable. In this example, we have a global variable a
. But inside the function iota
, we create a new local variable a
. The new local variable does not modify the global variable. But if we want to access the global value from within the function, we’d need to use the global window
object.
For us, this code would be much easier to read if we’d used a global namespace for our global variable and rewrite our function to use block scope:
Hopefully by now the following code should behave as you’d expect:
Code like this isn’t particularly readable. But the block scoped variable is a new variable accessible only within the block it is defined. Modifying the block variable will have no effect on the locally scoped variable outside of the block. The same will happen if we use let
to declare all variants of the variable a
. So we can rewrite the example like this:
We hope that this overview of scope has given you a better understanding of how JavaScript handles variables. Throughout this post, we have shown examples using var
, let
, and const
statements for declaring variables. With the arrival of ES6, we can now use let
and const
where we once would have used var
. Unfortunately, for many, this is still the approach they are now taking.
Does this mean that var
is redundant? Well, there is no right or wrong answer. We still like to use var
for defining global variables at the top-level. However, we use global variables sparingly and like to create a global namespace as discussed earlier for the few cases where these are needed. Otherwise, for any variables whose values will never change, we’d use const
and, for everything else, let
.
At the end of the day, it is up to you on how you declare your variables. But hopefully now you have a better understanding on how they will scope in your code.