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
orconst
instead ofvar
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();