While writing an article on closures and IIFE some days back. I was trying to explain lexical scope in a short and simple way. I noticed the article was getting bigger and bigger so i just decided to write about lexical scope separately.
In this article, i would try to explain the basics of lexical scope, what it means and give examples to help you understand how it works in JavaScript. No buzzwords...I promise.
Firstly, let's break down the word "Lexical Scope".
scope
We'll talk about the easy one first: scope.
In plain English, scope can mean:
- A boundary
- A region
- An environment
Or any other synonym you could think of.
Do you remember watching a tutorial where the instructor tells you a certain idea/topic is beyond the scope of the video?
Or when a team from another department is working on a project and you tell them to add a feature you think is cool and they tell you: "That's beyond the scope of this project".
Well i think you get the gist. Scope is simply a region where something is allowed to operate at a given period of time.
In Computer Science, this could mean a region where some data exists and can be accessed. This data could be something like a variable.
In a language like JavaScript, we could define a scope by creating a block using curly braces: {...}
. We call this Block Scope. This means that variables declared inside this block can only be accessed within this region. This region covers everything inside it, including other child/inner blocks created within that region.
In other words this region is local
and its variables cannot be directly accessed from the outside world. So this could be called a local scope.
Variables declared inside functions are in the local scope of the function.
function doSomething() {
let name = "john";
console.log(name)
}
doSomething(); // Prints 'john'
// would produce a Reference error
// because name is local to doSomething() function's scope
console.log(name);
Now, you might have heard of Global Scope before. Well, that's an environment which is not enclosed in a block. Every code in your Javascript environment has access to it. This is the open JavaScript environment. So the code below should work.
let name = "john"; // In the global scope
function doSomething() {
name = "James"
console.log(name); // The function can access the global scope variable 'name'
}
doSomething(); // Prints "James"
console.log(name); // Can access 'name' too
One thing to note:
A scope has access to its parent scope but the parent scope does not have direct access to variables declared in an inner scope.
The function has access to name
because the function is declared in the global scope and name
exists in the global scope. If name was declared inside the doSomething()
function, then the code in the global scope cannot change the value of name
directly because name
is local to the function.
I believe you have a good understanding of what Scope means. It just simply means a region or environment where a variable exists and can be accessed or modified.
Now, let's talk about "Lexical"
Lexical
To understand lexical, let's first look at the word from which it is derived from: Lexicon.
Lexicon was derived from the latin word: "lexis" which means "word"
In plain English:
Lexicon simply means a dictionary. In order words, it means a vocabulary of a person's language. It is like a book where the meaning/definition of words are stored.
Whenever you want to find the meaning of a word, you go to the lexicon.
You don't look at where the word is used and guess what it means and its content or value. You always go the lexicon, where the word's purpose is created and clearly defined.
Now with that explanation:
Lexical simply means something related to the lexicon. In other words, it means something related to words or vocabulary of a person's language. Something related to the creation or definition of words.
Let's talk about lexical scope.
Lexical Scope
We have seen the meaning of the two words in plain English.
With that knowledge, let's define Lexical Scope in plain English:
Lexical Scope simply means that the region in which a word exists is determined by where it was defined or created.
Other definitions would be:
Lexical Scope means that the meaning/value of a word can only be determined by the region/environment where it was created.
Lexical Scope means that you don't directly outsource the meaning of a word to people from an outside region that uses the word. This is because lexical places emphasis on the origin on where it was created/defined.
Okay, i'll give an example.
Let's use the word: "Dance".
The word "dance" was created/defined in Britain. The British people know its meaning. This word exists in the scope in which it was created: "Britain". Wales is in Britain so Wales has access to this word (remember we already explained why above). So the Welsh people can update the meaning of this word to suit their local dialect. This is because they are inside the scope of Britain.
The Germans cannot directly come and change the meaning of this word. This is because the word was not created in Germany. So, if the Germans wanted to use the English word: "dance" and that word has not yet been created by Britain, that word would not be available no matter how hard they try. This would make no one to know the actual meaning of that word in Germany because the word doesn't exist in the British Lexicon. (Don't worry if this seems gibberish, i'll explain with code later on)
Since we're students of Science and not Linguists, let's replace "word" with "variable".
Our new definition would be:
Lexical Scope simply means that the region in which a variable exists is determined by where it was defined or created.
Lexical Scope means that the meaning/value of a variable can only be determined by the region/environment where it was created.
Lexical Scope means that you don't directly outsource the meaning of a variable to code from an outside region(block) that uses the variable. This is because lexical places emphasis on the origin on where the variable was created/defined.
So what lexical scope shows us is that a variable can only be used in the scope in which it was created and not where it was called.
Let's see how this works in code:
function rideBritishBoat() {
let boatName = "Queen's Dab"; // local variable
return `Driving ${boatName}`
}
function rideGermanBoat() {
const status = rideBritishBoat();
return status;
}
rideGermanBoat();
The example above simulates a scenario where the Germans bought a boat from Britain....(You could swap it which ever country you want...no need to fight why i didn't mention some other country. These are just country names and not JavaScript libraries π ).
The rideGermanBoat()
uses the rideBritishBoat()
.
Since JavaScript uses lexical scope, when executing the rideBritishBoat()
function, it goes to where it was created and gets the reference of the variable: boatName
. So with lexical scoping, whenever rideBritishBoat()
is executed, JavaScript goes inside the function's scope to look for the variables used in this function.
Note: The scope of the rideBritishBoat()
function is its local scope and the global scope. The rideGermanBoat()
is not in the lexical scope of the rideBritishBoat()
function because rideBritishBoat()
was not created inside it.
Now, let's change the example a bit:
function rideBritishBoat() {
return `Driving ${boatName}`; // Reference Error: boatName not defined
}
function rideGermanBoat() {
let boatName = "Merkel's Dab";
const status = rideBritishBoat();
return status;
}
rideGermanBoat();
The above code fails. The rideBritishBoat()
functions fails to be precise. It fails when trying to access boatName
in the return statement.
Why?
This is because JavaScript uses lexical scope.
How this works is when it encounters boatName
variable inside the rideBritishBoat()
function, it looks for where the boatName
variable was created in its Scope Chain. That is, all possible scope of that function which is the: Local Scope of the function, then it checks its enclosing scope in this case the Global Scope.
So that is how JavaScript checks for variables. It first checks the local block in which the current variable is used to know if it was declared there. If it wasn't, then it goes up to the enclosing scope and keeps going if it doesn't find a declaration till it reaches the top of the chain which is the global scope
There is one other type of Scoping called "Dynamic Scoping".
The previous code would work in a language that supports dynamic scoping(e.g Lisp).
This is because in a dynamically scoped environment, the variable is checked at run-time. What is means is that when you execute rideGermanBoat()
and execution gets to rideBritishBoat()
the runtime environment checks for the value of boatName
where the code is currently running. In this case it finds it, so no problem and the code works at expected and prints out Driving Merkel's Dab
.
Lexical Scope is also called Static Scope because its the scope is determined at compile-time. Meaning it's environment/scope is fixed and can't just change. In other words, variables can only be called from within the block of code in which it was declared/created.
Dynamic Scope is called dynamic because its environment (outer scope) can change. In other words, variables can be called from outside the block which they are created.
So we could have another function that uses the rideBritishBoat()
called rideMauritianBoat()
:
function rideMauritianBoat() {
let boatName = "Flying Dodo's Dab";
const status = rideBritishBoat();
return status;
}
rideMauritianBoat();
In a dynamically scoped language, you can see the value of boatName
variable inside rideBritishBoat()
is dependent on the scope in which it is executed. As we can see that this scope can change, hence it is dynamic.
So inside rideBritishBoat()
, it calls the boatName
variable of rideMauritianBoat()
which is outside its block scope.
That's Dynamic Scoping and Lexical Scoping is the opposite.
But remember, JavaScript is not Dynamically scoped. This is just to show you the difference.
So lexical scoping checks for variables at compile-time (variables needs to be created and accessible in the scope/block it is used) while dynamic scoping checks for variables at run-time (variables might not be created in the scope when compiling but can be present when the function is running).
Senior Devs be like: Dude!! JavaScript is not a compiled language!!
Please, Let's leave that talk for some other day. Just try get to get the message i'm passing.
Okay, i'm pissed right now. Here's an exercise for you.
Quick Exercise
What would be the output of this function?
function rideBritishBoat() {
let boatName = "Queen's Dab";
function rideWelshBoat() {
boatName = "Welsh Royal Boat";
console.log(boatName)
}
rideWelshBoat();
}
rideBritishBoat()
Summary
The aim of this article was to explain lexical scoping in a simple way using basic grammar and short examples. If you noticed some words are in bold. Those are the key words to understand this concept. Plus, i have lots of alternate definitions of the same concepts. This was done for you to select which one sinks in easily for you. Different strokes for different folks π
From what we've learnt above we can say that:
- A scope is an environment/region in which something (a variable) exists
- A scope can access its parents.
- A parent scope does not have direct access to variables declared in an inner scope.
- Lexical has do do with where a variable was declared/created.
- Lexical scope enforces finding variables from the scope/block they were created/declared and not the environment which they are running in.
- Dynamic Scope is the opposite of Lexical Scope.
- Dynamic scoping checks the variables from where they are running.
Thanks for reading.
I'll see you in the next post.
Top comments (21)
Hey man. I am loving your work. Shoot me an email at kelvin at sailscasts dot com. We might be able to do something together :)
Thanks. I'm glad you enjoyed it.
Still waiting for that email π. Got an offer for you.
Have you used GetX with Flutter?
Nope. But it is super easy to use according to the community and it's everyone's favorite. I'll use if there's a need or i feel like using it on a project. Might make a video about it since you mentioned it ;)
Cool. I find it saner than most state management solutions in Flutter. And it's primarily what we use at Sailscasts HQ :)
Just for this article i join to the community is the simply way to explain complexity was great
Wow!!!
Thanks so much man.
Welcome to the community :)
Thanks to you bro :) with this understanding I hope to reach I good level in problem solving
Walsh Royal Boat
Lol.
Updated.
Such a great explanation. Thanks a lit.
You're welcome.
I'm glad you liked it :)
Here in 2021 and I love how clearly you explained this! Thank you for taking the time to break it down.
Ah! Nice!!! You're welcome man.
Your explaining skills is top notch!
Thanks man.
I'm glad you liked it :)
I really like the way you explained it!
Thanks so much.
I'm glad you liked it :)
These {...} are called curly braces, not parentheses :)
Thanks for the catch.
Updated.