JavaScript Primer: Unlocking Scope

Connecting variable declarations and variable scope in JS

Zach Morgan
6 min readNov 15, 2022
Photo by Tim Evans on Unsplash

Transforming ideas from imagination into reality. In a broad sense, that is the main goal of programming. A programming language is a tremendously powerful tool for building things and expressing ideas. As with any other tool, skillful use requires a lot of practice and a deep understanding of how the tool works. A programmer who masters the fundamentals of a language is able to leverage the language’s strengths, circumvent its weaknesses, and find enjoyment in its quirks. Mastery of fundamentals is especially valuable for a language like JavaScript — one of the quirkier languages in the modern tech stack — having numerous strengths and a few head-scratching weaknesses.

I have the privilege to be learning JavaScript as part of my studies at Launch School. My goal with this series of articles is to introduce a few of the language’s core topics in a way that is clear, ordered, and concise. This article will cover variables and scope. The articles to follow will dive into hoisting and closures — two frequently misunderstood features of the language. Developing a strong grasp of these topics is a worth-while investment that will pay off down the road.

Declaring Variables

Take a look at the following code:

let is the preferred keyword for declaring variables. It causes the computer to set aside a location in memory, and label it with the identifier that follows let, in this case name. This whole process constitutes variable declaration. Since name is not given a value here, JavaScript gives it the value undefined by default.

Let’s start over:

Now in addition to declaring a variable called name, we give it an initial value of 'Charlie'. Any expression following the = in a variable declaration is called an initializer. The expression will be evaluated, and the value it returns will be stored in the variable. Note that this is not the same thing as re-assignment, as re-assignments are stand-alone expressions. Let’s take a look at one.

Now that name has been declared in memory, we don’t need the let keyword when giving it a value. Also note that we don’t need to worry about re-assigning a variable to a value with a different data type. Although name was initialized to a string, we are able to seamlessly re-assign it to a number. This is because JavaScript is dynamically typed.

In addition to let, the const keyword can be used to declare variables. The only difference between the two is that variables declared using const must be initialized to a value, and cannot be re-assigned after that.

Block Scope

In JavaScript, a block is a set of statements and expressions between a pair of opening and closing curly braces. All variables that are declared using let or const are said to have block scope. What this means is that variables declared outside of a block can be referenced inside of a block. However, variables that are declared inside of a block cannot be accessed outside of the block. An example will make this more clear:

On line 1, we declare a variable dog and initialize it to 'Charlie'. Then on lines 3–6 we have an if statement with a block. Since variables declared with let have block scope, dog can be accessed inside the block. In this code, we choose to re-assign dog to 'Ruby'. On the next line, we declare a new variable cat and give it an initial value of 'Scratch'. Since this variable is declared inside of the block, it cannot be accessed outside of the block. We can demonstrate this by adding two more lines to the bottom of the code snippet:

In this code, we pass the value of each variable as an argument to console.log(), which prints the values to the console. When line 8 executes, "Charlie” is displayed on the console. However, when the next line runs, JavaScript throws a ReferenceError, saying that the variable cat is not defined. Even though the variable cat exists, it is not available to be referenced outside of the block where it was declared.

While we are on the topic of block scope, there is an important nuance to be aware of. The curly braces which wrap around the body of a function are not technically a block. However, they behave just like a block when it comes to variables that have block scope.

Function Scope

let and const are actually relatively modern ways to declare variables. They were introduced to JavaScript in 2015 as part of ES6, which added many new features to the language. The traditional way to declare variables is with the var keyword. There are a handful of differences between variables declared with var and those declared using let and const. We will discuss one of them here and some others in the article about hoisting.

Variables declared using var have function scope. This means that they are not affected by blocks when it comes to scope, and are only influenced by function bodies. Lets take a look at an example:

We see on line 3 that even though the variable name is declared inside a block, it is available to be referenced later in the function on line 6 when its value is passed as an argument to console.log(). However, an error is raised on the last line of code when we try to use name outside of the function.

In addition to variables declared with var, function declarations also create variables with function scope. Let’s take a look:

Here we have a function called sayHello that is declared inside of the function saySomething. That declaration is actually declaring a variable called sayHello, and initializing it to the sayHello function. As we would expect, we can invoke sayHello() by referencing it anywhere inside of the saySomething function. However, if we try to call sayHello() outside of the function body of saySomething, JavaScript won’t let us.

Local vs. Global

The keyword that is used to declare a variable is only one of the factors that can be considered when talking about scope. Another factor is the location in the program where the declaration occurs. Based on this, variables fall into one of two categories — global variables and local variables.

Global variables are those which are declared in the top-level scope. This means that their declaration does not occur inside of a block or function body. Since all variables can be access from an inner (or more narrow) scope relative to where they are declared, global variables are accessible everywhere in a program.

Local variables are those which are declared inside a block or function body. They are said to have local scope because they can only be accessed inside that artifact, and any that are more narrowly scoped.

Variable Shadowing

When a variable is declared in an inner scope with the same name as another variable in an outer scope, variable shadowing occurs. This causes the variable in the outer scope to become inaccessible. All references which use the variable name point to the variable that was declared in the inner scope. Getting confused yet? An example will help:

After declaring a variable dog on line 1, we have another variable declaration with the same variable name on line 4. Since this happens in an inner scope, a new dog variable is declared and initialized to 'Ruby'. This is a different variable than the dog that was declared on line 1. We see evidence for this in the remaining code. Although dog has a value of 'Ruby' when line 5 executes, when dog is referenced outside of the block on line 7, the variable from line 1 is accessed and reports a value of 'Charlie'.

Here is another scenario where variable shadowing can occur:

Under normal circumstances, the variable dog which is declared on line 1 would be accessible inside of the petTheDog function. However, in this case the function parameter has the same name as this variable. Since function parameters create local variables (local to the function), the parameter dog shadows the variable dog from the outer scope. That is why when dog is referenced inside the function on line 4, it returns the value of the argument passed to the function instead of the value of the dog global variable.

We have made it through a whirlwind introduction to one of the most fundamental topics in JavaScript. Understanding variable scope is essential to crafting robust programs in any language. JavaScript is no exception.

One of the beautiful facets of JavaScript is that it is a forgiving language in many ways. A functional program can be hack-and-slashed together with minimal knowledge of how the language works. But as with any language, building something capable, maintainable, and long-lasting is much easier if you understand the language well. It can also be remarkably fun.

This article is a precursor to my articles on hoisting and closures in JavaScript. If those topics interest you, feel free to check them out!

--

--