8. Variable Scope

Objective: By the end of this checkpoint, you will have better understanding of  ‘variable scopes’.

Key Terms

  • variable scope
  • global scope
  • local scope
  • block scope
  • let
  • const

Variable Scope in Modern JavaScript

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 lesson, we are going to run through a number of JavaScript examples to show how globallocal, and block scopes work. We will also look at let and const statements for anyone who is not already familiar with these.

Global Scope

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 objectglobal.

It’s important to be aware that if you do not declare a variable using one of the keywords varlet, 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.

Local Scope

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.

Block Scope

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

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.

const

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.

Global + Local Scope

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:

Local + 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:

Use var, let, or const?

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 varlet, 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.