What is JavaScript lexical scope?

Fortune Ekeruo


Introduction

JavaScript scope is basically the accessibility of variables or values within specific sections of your code during its execution. This is to provide security, reduce errors and make your code predictable, which is a principle applied to most programming paradigms (aka Principle of Least Privilege).

There are two types of scope in JavaScript:

  • Global Scope
  • Block/Function Level Scope

Global Scope

Just as the name implies, values or variables declared within this scope are available throughout the code and can be accessed anywhere.

See an example below

let name = 'Fortune';
let age = 22;

function func() {
  // Both name and age can be accessed here
  console.log(`The name is ${name}, and I'm ${age} years old`);
}

func(); // -> 'The name is Fortune, and I'm 22 years old'

The code above shows that the variables name and age are declared in the global scope, which makes them accessible everywhere including inside the function called func.

The global scope can be the window or document object of your browser, it can also be the process object on a Node.js environment.

Block/Function Level Scope

This is also called the local scope, values or variables declared within a function cannot be accessed outside that function.

Even though functions can access variables in their outer scope, the reverse is not the case. Any value or variable that is not declared within a function is a global variable and can be accessed anywhere in that program file.

Example

let name = 'Fortune';

function func() {
  let age = 22;
  console.log(name, age);
}

func(); // 'Fortune 22'

// Accessing age here throws
console.log(age); // Uncaught ReferenceError: age is not defined

/**
 * Variable declared without the var, let or const keyword
 * gets thrown into the function's outer scope
 */
function withoutVarLetOrConst() {
  out = 'Acessible within the outer scope';
}
withoutVarLetOrConst();
console.log(out); // -> 'Accessible within the outer scope'

// Variable "name" can be changed within a function scope
function changeName() {
  name = 'Rose';
}
// Calling the function modifies the global variable "name"
changeName();

console.log(name); // -> 'Rose'

The variable age was declared within the func function which means it cannot be accessed anywhere outside that function.

However, it’s worth noting that variables declared within a function without the var, let or const keywords becomes accessible from the outer scope when that function is called as seen with the out variable above.

Also, global variable values defined without the const keyword can be changed within a local scope by just reassigning that variable, as shown in the function changeName which changes the value of the global variable name.

Most developers consider setting variables within the global scope as a bad practice as it leads to confusions and makes fixing bugs difficult.

Block Statements

Blocks statements are if, switch, for and while loops, variables declared within them using the var can be accessed from their outer scope. When I create a variable with the var keyword within a block statement such as if, then that variable is also accessible from the outer scope.

A block statement is not a scope but if a variable is declared with the ES6 let or const keywords, then it's only accessible within that block statement.

Example

for (var i = 1; i <= 5; i++) {
  console.log('Number: ' + i);
}

// variable i is accessible here
console.log(i); // -> 6 - which is the last value of i incremented by 1

/**
 * However, using the ES6 let or const keyword to declare the variable
 * makes it behave like a scope as it's only accessible within the for
 * loop block.
 */
for (let x = 1; x <= 5; x++) {
  console.log('Number: ' + x);
}

// Access variable j outside the block throws
console.log(x); // -> ReferenceError: x is not defined

The variable i defined with the var keyword within the first for loop block is accessible within its surrounding scope (The global scope in this case), but using the let or const keyword prevents that as seen in the second for loop block.

Hoisting

Consider the code below

console.log(age); // -> undefined
var age = 22;
console.log(age); // -> 22

console.log(location); // throws -> Uncaught ReferenceError: location is not defined
location = 'Lagos, Nigeria';

I tried to log age before assigning it to a value but I got undefined and not an error as you'd expect. This is so because when a variable is declared with the var keyword then that variable is automatically initialised to undefined and by default hoisted to the top of its available scope.

The JavaScript engine sort of restructures the code behind the scenes before execution, which mean it first declares the variables right before they're assigned a value.

However, variables declared without the var, let or const keywords are not hoisted as is the case with the location variable in the example code above.

On the other hand, functions are hoisted a little differently from variables.

Consider the code below.

console.log(func1);
console.log(func2);

let func1 = function () {
  // do something
};

function func2() {
  // do another thing
}

The first function func1 is initialized to undefined just as expected but func2 is hoisted in its entirety, which means that it’s completely accessible right at the top unlike func1 which is only but undefined at the top.

In a given Javascript turn, functions are initialized first before variables, the JavaScript engine parses all the syntax, allocates memory to the functions first then the variables. Hence, making func2 accessible before it was defined.

Conclusion

Understanding how the JavaScripts scope works will improve the efficiency of your code and help you track bugs easily. I really hope this article has helped you understand it much better.

If you spot something that needs correction or better explanation, please tweet me @fortune_ik.

Checkout my article on how the concept of this keyword works.

Happy Coding!