1. JavaScript
  2. Fundamentals
  3. Data Containers & Scoping

JavaScript Data Containers & Scoping

Last updated:

JavaScript provides a couple of different ways to store and manipulate data - variables and constants are containers that can be referenced by an arbitrary name and can be accessed and modified throughout the program.

Constants

Containers that store immutable data (content cannot be changed) - use the keyword const:

const theAnswer = 42

Re-assignment triggers an error:

const theAnswer = 42
theAnswer = 12   // Uncaught TypeError: invalid assignment to const 'theAnswer'

Variables

Containers storing data that can be changed during the execution of a program (mutable) - use the keyword let:

let colour = 'blue'
console.log(colour)  // Output: blue
colour = 'red'
console.log(colour)  // Output: red

“Legacy” Variables

The var keyword can also be used to declare a variable in JavaScript - it was the “original” and only way to do it for a while:

var colour = 'blue'

The use of var is being discourage because of unpredictable side effect:

let vs var

Both var and let keywords can be used to declare variables with the main difference between them is the scope of the variable: var is function scoped, whereas let is block scoped. If you declare a variable using var inside a function, it will be accessible anywhere within that function, but if you declare a variable using let inside a block, it will only be accessible within that block.

Hoisting

Hoisting is a mechanism where variable and function declarations are moved to the top of their scope before code execution - regardless of where they are declared in the code, they are processed first before the code is executed.

Variable Hoisting

When a variable is declared using the var keyword, it is hoisted to the top of its scope so can be accessed and used even before it is declared in the code. But, its value will be undefined until assigned a value.

console.log(name); // Output: undefined
var name = "Paul";
console.log(name); // Output: Paul

Function Hoisting

Functions declared using the function keyword are also hoisted to the top of their scope so can be called before they are declared in the code:

sayHello(); // Output: Hello World!

function sayHello() {
    console.log("Hello World!");
}

Hoisting in ES6

ES6 introduced the let and const keywords for declaring variables. Unlike variables declared using var, variables declared using let, and const are not hoisted to the top of their scope, therefore cannot be accessible before declaration.

console.log(name); // Uncaught ReferenceError: name is not defined
let name = "Paul";

In the above example, since name is declared using let, it is not hoisted to the top of its scope, so it results in a ReferenceError.

It is recommended to use let or const instead of var to avoid hoisting-related issues.


Scope

One of the key concepts in JavaScript - it refers to the area of a program where variables and functions are accessible.

Global Scope

Global scope refers to variables or functions that are accessible throughout the entire program. Variables and functions declared outside of any function have global scope.

greeting is declared outside of any function, making it a global variable that can be accessed from anywhere in the code:

var greeting = "Hello World!";

function sayHello() {
  console.log(greeting);
}

sayHello();   // Output: "Hello World!"

Local Scope

Local scope refers to the area of a program where variables and functions are available - they are only accessible within a specific function or block of code.

var scoping

function sayHello() {
  var name = "John"
  console.log(`Hello ${name}`)  // Output: "Hello John"
  if(true) {
    var name = "Paul"
    console.log(`Hi ${name}`)  // Output: "Hello Paul"
  }
  console.log(`Hello again ${name}`)  // Output: "Hello again Paul"
}

sayHello()

The snippet above creates only one variable name - the second declaration gets ignores and the content of name will be changed to Paul.

NB: This behaviour can be prevented if strict mode is used.

Function Local Scope

In the example below, the greeting variable is defined inside the sayHello function and only accessible within it. The attempt to access it outside the function results in a ReferenceError:

function sayHello() {
  var greeting = "Hello World";
  console.log(greeting);   // Output: "Hello World"
}

sayHello();
console.log(greeting);
  // Uncaught ReferenceError: greeting is not defined

The same outcome will occur when using let instead of var.

Block Local Scope

let is block scoped - it will only be available inside the block of code it is defined in:

var greeting = "Hello World"
if(true) {
  let greetingPaul = "Hello Paul";
  console.log(greeting);      // Output: "Hello World"
  console.log(greetingPaul);  // Output: "Hello Paul"
}

console.log(greeting);      // Output: "Hello World"
console.log(greetingPaul);
  // Uncaught ReferenceError: greetingPaul is not defined

When used inside a function, the same rules apply:

function sayHello() {
  if(true) {
    let name = "John"
  }
  console.log(`Hello ${name}`)
}

sayHello()  // Output: Hello
function sayHello() {
  let name = "John"
  console.log(`Hello ${name}`)  // Output: "Hello John"
  if(true) {
    let name = "Paul"
    console.log(`Hi ${name}`)  // Output: "Hi Paul"
  }
  console.log(`Hello again ${name}`)  // Output: "Hello again John"
}

sayHello()

Notice that the 3rd message includes the value of the first name variable.

Nested Local Scopes

A function has its own local scope, and any nested function inside that function also has its own local scope. The inner function can access the variables declared in the outer function, but the outer function cannot access the variables declared in the inner function.

function outerFunction() {
  let outerVar = "I am in outer function";
  function innerFunction() {
    let innerVar = "I am in inner function";
    console.log(outerVar); // Output: "I am in outer function"
  }
  innerFunction();
  console.log(innerVar);
    // Output: Uncaught ReferenceError: innerVar is not defined
}

outerFunction();