Get an in-depth view of Scopes in JavaScript
Understand the scope, its types, scope chaining, lexical scope, and lexical environment in JavaScript
Topics covered are:
- 🔥 Scope (along with its types)
- Lexical scope
- 🔥 Scope chaining
- Lexical environment
- Difference between lexical scope and the lexical environment
Scope
The scope of something is where can I access that something. It reduces the issue of namespace collisions. There are 3 types of scope in JavaScript:
- 👵🏻 Global scope
- 👩🏻🦱 Function scope
- 👧🏻 Block scope
Global scope
Anything that’s not inside the function OR block (only curly braces {}
like if-else
and loops
).
let food = "🍞";
const eat = () => { console.log(food); }; // 🍞
Function scope
Things defined inside a function can only be accessed inside that function. They are the local states of that function.
const eat = () => {
let food = "🍞";
console.log(food); // 🍞
};
console.log(food); // ReferenceError: food is not defined
Block scope
Prior to let
and const
, variables defined using var
inside blocks such as if-else
, loops
, etc… would leak out of the scope where they were defined. This lead to “nonsense behaviors” such as:
var food = "🍔";
if (true) { var food = "🍕"; }
console.log(food); // 🍕
let
and const
solved this issue by scoping the variables defined inside a block, to that block.
var food = "🍔";
if (true) { let food = "🍕"; }
console.log(food); // 🍔
How scoping is done in JavaScript? Before understanding that we will have to learn about Lexical Scope.
Lexical Scope
Lexical scope, also known as Static Scope, is the process of determining the scope of a variable during the compile time is called lexical scoping and the scope we get is called lexical scope. Lexical scoping is fundamental to the ways nesting (Nested scope, more on this later) occurs. For an example, consider the following code:
function parent() {
function child() {
let food = "🍯";
console.log(food);
}
child();
}
parent();
Here, as a developer, you made a choice of keeping the food
variable inside the child
function’s scope.
Also here we’ve 🔥 Nested Scope which is nothing but the concept that in JavaScript scope can be nested inside another scope. In this example, the child
scope is nested inside the parent
scope which in turn is nested inside the global scope.
The term lexical refers to the parsing/tokenization/lexical stage of a compiler. In this stage, your code is divided into tokens. For example:
const drink = "🍷";
// Tokens - const, drink, =, "🍷"
Recommended - watch this video to learn about Lexical Scope in depth
On a side note, this is different from dynamic scope (which is found in bash, LaTex, and academic languages). Lexical scope is faster than Dynamic scope. In dynamic scope, your search in the local function first, then you search in the function that called the local function, then you search in the function that called that function, and so on, up the call stack. “Dynamic” refers to change, in that the call stack can be different every time a given function is called, and so the function might hit different variables depending on where it is called from.
An example of lexical scoping:
let drink = "🍷";
const globalDrink = () => {
console.log(drink);
};
const localDrink = () => {
let drink = "🍺";
globalDrink()
};
localDrink(); // 🍷
- Output for Lexical scope (JavaScript) - 🍷
- Output for Dynamic scope (bash) - 🍺
The next topic is about how lexical scope helps to get variables, Scope Chaining.
Scope Chaining
Using this lexical scoping we get a scope chain, traversing in which JavaScript finds the variable declaration.
When you use a variable
, JavaScript tries to find its value
in its local scope. If not found then it goes to its parent’s scope and looks for the variable
. If found then that value
of the variable
will be used. But if it didn’t find the value
then it will go to that parent’s parent scope (grandparent) and so on till the variable
's declaration is not found. The last scope will be the global scope, if the variable
isn’t found there then you will get the ReferenceError.
Examples of different cases:
// food not declared in the scope chain
function grandparent() {
function parent() {
function child() {
console.log(food);
}
child();
}
parent();
}
grandparent(); // ReferenceError: food is not defined
// food is declared in the scope chain - its own scope i.e. child's scope
function grandparent() {
function parent() {
function child() {
let food = "🍯";
console.log(food);
}
child();
}
parent();
}
grandparent(); // 🍯
// food is declared in the scope chain - its outer scope - parent's scope
function grandparent() {
function parent() {
let food = "🍇";
function child() {
console.log(food);
}
child();
}
parent();
}
grandparent(); // 🍇
// food is declared in the scope chain - its outer scope - grandparent's scope
function grandparent() {
let food = "🍉";
function parent() {
function child() {
console.log(food);
}
child();
}
parent();
}
grandparent(); // 🍉
// food is declared in the scope chain - its outer scope - global scope
let food = "🥥";
function grandparent() {
function parent() {
function child() {
console.log(food);
}
child();
}
parent();
}
grandparent(); // 🥥
But how is this different from the term Lexical Environment?
Lexical environment
In literal terms, it's the “environment OR space” inside curly braces {}
.
// Lexical env 1
function outer() {
// Lexical env 2
function inner() {
// Lexical env 3
}
}
In technical terms, it's a structure like a map OR a dictionary {}
where
- the keys are identifiers i.e. variable/function/etc… names and
- the values are
- if its primitive value then directly values else
- it will store the reference of that object (eg: for array, function, etc…)
let food = "🍕";
const getFood = () => { console.log(food); };
lexicalEnvironment = {
food: "🍕",
getFood: <ref. to the object>
};
🔥 A lexical environment has access to variables/functions defined in the outer lexical environment but cannot access other lexical environments created inside them. Therefore the full structure of the lexical environment is:
lexicalEnvironment = {
food: "🍕",
getFood: <ref. to the object>
outer: <outer lexical environment>
};
Difference between Lexical Scope and Lexical Environment
The lexical scope for an object is where the object can be accessed during the compile time. Whereas lexical environment is a structure where these objects are stored with their name and values/references. 🚨 Caution - lexical scope and lexical environment are used interchangeably and that’s wrong, they are different things.
Conclusion
The scope is about where an object can be accessed. Scopes are created using Lexical scoping which happens during the compile time. Using lexical scoping we get a scope chain, traversing which we get the object’s declaration. If the declaration isn’t found we get a ReferenceError. At last, the information about the object names and their values are stored in a structure named Lexical Environment.
If you liked this post then do 👍🏼 like and give your 🎙 feedback. ✅ Follow to stay up to date with my new posts.