Lexical scope is a somewhat intimidating concept the first time you hear about it. I don’t know about you, but the name alone doesn’t tell me much. I remember that for me, it was one of those concepts that I had to make a conscious effort to learn; it wasn’t something that came naturally to my mind like the concept of a Pokéball or a Kamehameha.
However, it’s not really that complex. And in fact, the name does make sense, but only once you learn about topics like lexical analyzers, and that… is beyond the scope of this post (lol).
Basically, when you’re programming and you create a variable or function, you can’t read that element from just anywhere. The Lexical Scope refers precisely to the scope that your element has, or in other words: from how many places you can access it.
And like many things in life, we also classify lexical scope according to its size:
- global scope – It has a scope at… a global level. Throughout the entire program.
- local scope – It has scope only within a specific function.
- block scope – It has scope only within a code section/block, such as for, while, if, else.
We could leave the explanation there, but let’s see some illustrations with examples, just to make it clearer.
Imagine an amusement park. A normal park, with some attractions and food stands. Nothing out of this world.
Like any good member of society, you buy your ticket (one of the best-designed tickets for a park) and get ready to enter.
Global Scope
The Global Scope is where JavaScript typically gathers all the elements declared in one or more files. Essentially, it is the central place where all the main references can be accessed.
In an amusement park, visitors, of course, need their ticket to enter. Any employee in any attraction can ask you for the ticket to verify that you have indeed purchased your admission.
The green area represents the entire global scope. Your ticket is valid for any element found within the global scope.
So any global element is accessible from any part of the code:
let ticket = 'Awesome Ticket';
function hauntedHouse() {
// TODO: Haunted House stuff
console.log('ticket called from "hauntedHouse" --- ', ticket);
}
function rollerCoaster() {
// TODO: Roller Coaster stuff
console.log('ticket called from "rollerCoaster" --- ', ticket);
}
function arcade() {
// TODO: Arcade stuff
console.log('ticket called from "arcade" --- ', ticket);
}
function restaurant() {
// TODO: Restaurant stuff
console.log('ticket called from "restaurant" --- ', ticket);
}
console.log('ticket called from "global" --- ', ticket);
hauntedHouse();
rollerCoaster();
arcade();
restaurant();
What do you think will happen if we run this program? Well, since the ticket is accessible to everyone, it will print 5 different lines.
ticket called from "global" --- Awesome Ticket
ticket called from "hauntedHouse" --- Awesome Ticket
ticket called from "rollerCoaster" --- Awesome Ticket
ticket called from "arcade" --- Awesome Ticket
ticket called from "restaurant" --- Awesome Ticket
Local Scope
The Local Scope, in contrast to the global scope, is defined only as the area that a function encompasses.
Suppose this park offers the possibility of purchasing VIP passes for a couple of places:
What these passes do is basically accumulate points for future visits within these 2 places.
As you can imagine, this means that the VIP Arcade pass is only valid in the arcade and the VIP Restaurant pass is only valid in the restaurant.
Let’s see an example of this logic in the code:
let ticket = 'TICKET';
...
function arcade() {
let arcadeVIPPass = 'VIP-Arcade';
console.log('arcadeVIPPass called from "arcade" --- ', arcadeVIPPass);
}
function restaurant() {
let restaurantVIPPass = 'VIP-Restaurant';
console.log('restaurantVIPPass called from "restaurant" --- ', restaurantVIPPass);
}
console.log('ticket called from "global" --- ', ticket);
console.log('arcadeVIPPass called from "global" --- ', arcadeVIPPass);
console.log('restaurantVIPPass called from "global" --- ', restaurantVIPPass);
arcade();
restaurant();
What do you think will happen when trying to run this example?
Ticket called from "global" --- TICKET
...\src\lexicalScope\2-localScope\tempCodeRunnerFile.js:14
console.log('arcadeVIPPass called from "global" --- ', arcadeVIPPass)
ReferenceError: arcadeVIPPass is not defined
Well, just as I said. It’s not possible. The code fails because the global scope cannot find references to arcadeVIPPass
.
That variable only exists and is valid within the arcade
function. And of course, the error right now indicates that arcadeVIPPass
is to blame because it was the first one encountered. But restaurantVIPPass
is making the same mistake.
What if we remove the references to both variables in the global scope?
let ticket = 'TICKET';
function arcade() {
let arcadeVIPPass = 'VIP-Arcade';
console.log('arcadeVIPPass called from "arcade" --- ', arcadeVIPPass);
}
function restaurant() {
let restaurantVIPPass = 'VIP-Restaurant';
console.log('restaurantVIPPass called from "restaurant" --- ', restaurantVIPPass);
}
console.log('ticket called from "global" --- ', ticket);
arcade();
restaurant();
Now we’re good! The code makes sense now.
Ticket called from "global" --- TICKET
arcadeVIPPass called from "arcade" --- VIP-Arcade
restaurantVIPPass called from "restaurant" --- VIP-Restaurant
Block Scope
The block scope is similar to the local scope, but instead of referring to the area covered by a function, it refers to the area within a code block. Commonly used code blocks are <code>if, else, while, for
, etc
In the restaurant, there are shows at certain times. Anyone can attend them, but those with a VIP pass have a bonus. At the beginning of each show, they are given a ticket that is used for a raffle prize at the end of the show.
The ticket is only valid for that specific show and, of course, it is not valid outside the restaurant.
let ticket = 'TICKET';
function restaurant() {
let restaurantVIPPass = 'VIP-Restaurant'
console.log('restaurantVIPPass called from "restaurant" --- ', restaurantVIPPass)
console.log('raffleTickets called from "restaurant" --- ', raffleTickets)
if (typeof restaurantVIPPass === 'string') {
let raffleTickets = 10
console.log('raffleTickets called from "restaurant" inside the if statement --- ', raffleTickets)
}
}
console.log('ticket called from "global" --- ', ticket)
console.log('raffleTickets called from "global" --- ', raffleTickets)
restaurant()
First of all, you shouldn’t be surprised to see the same error at the global level as in the example of the local scope:
ticket called from "global" --- TICKET
...\src\lexicalScope\3-blockScope\tempCodeRunnerFile.js:15
console.log('raffleTickets called from "global" --- ', raffleTickets)
ReferenceError: raffleTickets is not defined
However, fixing that line is not enough. If you delete the line console.log('ticket called from "restaurant" --- ', tickets)
, you will get the same error, but this time within the restaurant()
function:
ticket called from "global" --- TICKET
...\src\lexicalScope\3-blockScope\tempCodeRunnerFile.js:7
console.log('raffleTickets called from "restaurant" --- ', raffleTickets)
^
ReferenceError: raffleTickets is not defined
As you already know, since raffleTickets
was declared within the if statement, you cannot use it outside of that block.
Therefore, for the code to work, you have to remove all references to raffleTickets
outside the block scope.
let ticket = 'TICKET';
function restaurant() {
let restaurantVIPPass = 'VIP-Restaurant'
console.log('restaurantVIPPass called from "restaurant" --- ', restaurantVIPPass)
if (typeof restaurantVIPPass === 'string') {
let raffleTickets = 10
console.log('raffleTickets called from "restaurant" inside the if statement --- ', raffleTickets)
}
}
console.log('ticket called from "global" --- ', ticket)
restaurant()
Y deberás ver el llamado a las 3 variables con los 3 scopes: global, local y block
ticket called from "global" --- TICKET
restaurantVIPPass called from "restaurant" --- VIP-Restaurant
raffleTickets called from "restaurant" inside the if statement --- 10
BONUS
If you pay attention to these examples, I was declaring variables using let
. Why? If you’re already familiar with const
, let
, and var
, you might have an idea. But if not, let me tell you quickly.
Depending on which keyword you use, you’ll get a different scope behavior. In this case, let
and const
are the keywords that allow me to explain the concept of lexical scope more directly. Using var
has other implications.
Why didn’t I use const
? Well, there’s no specific reason. I flipped a coin and decided to be consistent by using let
. I wouldn’t want someone to get confused if I combined the two.
But don’t worry my friend, if you want to know the implications of using const
, let
, and var
, you can click here.