Variable Scope in JavaScript

Variable Scope in JavaScript

Explore variable scope in JavaScript, including block scope and function scope, and how variable declarations impact the scope and scope chain.

·

6 min read

There are three ways to declare variables in JavaScript - let , const and var . There are subtle differences in how they behave, and this post discusses one of them - their scope.

What Is Variable Scope?

Imagine there are two rooms A and B, and you're in room A. With respect to room A, you're inside it and can be accessed within it. You're hence in the scope of A. With respect to room B, however, you're out of scope as you're outside the room and cannot be accessed within it.

Similarly, the variable scope is a block or region of code in which a variable can be accessed.

A simple example is given below. Try executing the code and notice the output. We'll get to the 'why' in the next section.

function add(a, b){
    const c = a + b;
}

add(3, 5);
console.log(c) // Uncaught ReferenceError: c is not defined

let & const

If you started learning JavaScript within the last few years and followed modern tutorials, chances are that you've only used let and const to declare variables. The difference between the two is that variables declared using let can be reassigned, while that declared using const cannot.

let and const follow what is called a block scope. A block is just any code within a pair of curly braces. You would have noticed blocks when defining functions, if-else statements, and loops.

Variables declared using let and const are only accessible within the block they are declared. Accessing them from outside the block will result in a Reference Error. Go back to the code snippet above. We have a function add inside which a variable c is declared. c is accessible within the code block of add but accessing it outside of the block throws an error.

An interesting thing to note is that you don't need to define functions or loops to create a block and implement block scope. You can use a pair of curly braces anywhere to create a block.

{
  const fullName = 'Abin John'
  console.log(fullName.toUpperCase()); //ABIN JOHN
}

console.log(fullName.toLowerCase()) //error

var

Before let and const were introduced to the JavaScript language with ES6 in 2015, var was the only way to declare variables. Like let, variables declared using var can be reassigned.

var follows what is called a function scope, which means that variables declared inside a function can be accessed from anywhere within it, but not outside it. At first glance, it might seem similar to block scope, but there is a subtle difference. I'll highlight that through an example.

Consider the snippet of code below that mimics the roll of a dice and displays a message depending on the dice value. The first snippet uses const for declaring the message variable. See if you can spot the error before running the code.

function rollADiceES6() {
  // generate a random number between 1 and 6
  const dice = Math.floor(Math.random() * 6 + 1)

  if(dice < 3){
      const message = 'ES6: You shall not pass!';
  }
  else {
      const message = 'ES6: You shall pass!'
  }
  console.log(message)
}

The error here is that message is defined inside the if and else blocks and hence, cannot be accessed outside of them. Kudos if you got it right! The next snippet uses var for declaring the message variable. Run this code and see what the output is.

function rollADiceVar() {
  // generate a random number between 1 and 6
  const dice = Math.floor(Math.random() * 6 + 1)

  if (dice < 3) {
    var message = 'var: You shall not pass!';
  } else {
    var message = 'var: You shall pass!'
  }
  console.log(message)
}

Surprised?

The code runs without any error because var isn't bound by the block scope. Curly braces don't affect the scoping of var variables. Variables declared inside a function will be accessible anywhere inside it. message is declared within the rollADiceVar() function and is accessible to the console.log() statement. Accessing message outside of the function will throw an error.

A Bit More About Scope

Global Scope

We learned about block scope, which is the region between two curly braces, and function scope, which is the function body. But what if variables are declared at the top level, outside of any block or function?

Such variables are bound by the global scope, which is a scope that's accessible everywhere. Therefore, variables declared in the global scope are accessible from anywhere in the code, irrespective of their declaration method. This is also the reason why these variables are referred to as global variables.

Scope nesting

Nesting blocks and functions inside one another is a common practice in programming (nested if-else statements, helper functions inside bigger functions, etc.) We now know that every block and function has its own scope. When nesting is done, inner scopes have access to their outer scopes.

In the code snippet below, when the line const b = a + 5 is encountered, JavaScript first searches for a in the inner scope. When not found, it searches the outer scope. In our case, a is present in the outer scope, but if it weren't present in the outer scope, JavaScript would keep traveling up the scope chain until the variable is found or the global scope is reached. If the variable is not found even in the global scope, a reference error is thrown.

{
  const a = 23;
  {
    const b = a + 5;
    console.log(b) // 28
  }
   console.log(b) // error
}

{
  const c = a - 10; // error
  console.log(c)
}

Scope chain only moves upwards i.e. an inner block can access the scope of the outer block, but not vice-versa. That's why line 7 in the above snippet throws an error. b is out of scope for the outer scope.

Scope Priority

Consider the snippet below. The variable firstName is declared both as a global variable and inside the whatIsMyName function, while lastName is only a global variable.

const firstName = 'Abin';
const lastName = 'John';

function whatIsMyName() {
  const firstName = 'Sam';

  console.log(firstName); // Sam
  console.log(lastName); // John
}

whatIsMyName()

In scenarios like these where the same name is given to variables of different scopes, the inner scopes have priority. firstName is present inside the function block, so its value is taken from there. lastName is not present inside the function block, so JavaScript moves one step up the scope chain to the global scope and finds lastName there. This order of priority is one reason why you should be mindful of variable names and ensure there are minimal conflicts. These bugs are tricky to catch as no error is thrown in most cases.

How Should You Declare Variables?

It is considered best practice to use let and const in contrast to var to declare variables because their scoping behavior is similar to many other programming languages. The block scope is clean and less prone to errors. There are a few more reasons why the use of var is not encouraged anymore, like hoisting and re-declaration, but that's outside the scope of this article (pun intended!)

You can check out this great post on why var is considered obsolete to know more.

Conclusion

Scoping might be a bit difficult to grasp at first. I had to sit on it for a few hours before I finally 'got it'. Play around with a few examples and you'll get it too. And remember to cut back on the usage of var and be conscious of variable names.

That wraps up this post! I hope you got to learn something new today. If you did, let me know in the comments or on Twitter

Did you find this article valuable?

Support Abin John by becoming a sponsor. Any amount is appreciated!