Introduction
Variables in JavaScript can be accessed depending on where they are declared. If they are declared in a global scope, they can be accessed anywhere in the program but if they are declared in a local scope which may either be function scope or blocked scope, they can only be accessed within that scope. This article aims to provide you with a brief overview of lexical scoping in JavaScript, types of lexical scoping, the scope chain, how lexical scoping is used in a program and its importance.
Lexical scoping
Scope refers to the environment where a variable is declared. Scoping refers to how variables are organized and accessed in a program. Lexical scoping deals with how nested functions and block scopes have access to variables declared in their outer scope.
For example:
const car = function () {
const carMake = "Benz";
const string = function () {
return `My car is a ${carMake}`;
};
console.log(string());
};
car(); // My car is a Benz
In the above example, the string()
function has access to the carMake
variable declared in its parent car()
function because both are in the same lexical scope.
In JavaScript, there are three types of scope: global scope, function scope and block scope. They will be examined below.
Global scope
Variables that are not declared in any function or block are said to be declared in the global scope. These variables are accessible everywhere in your program including in all functions and blocks.
For example:
// Variables declared within the global scope
const name = "Kingsley";
const job = "Student";
Function scope
Every function creates a scope. Variables inside a function are deemed to be function scoped. That is they are only accessible inside that function and not outside. Function scope can also be called local scope as opposed to global scope.
const addNum = function (num) {
const num1 = 2;
return num1 + num;
};
console.log(num1); // Uncaught ReferenceError: num1 is not defined...
If you run the code above in your code editor, you will notice that you cannot access the num1
variable outside of the addNum
function as it will turn up a ReferenceError
.
Block scope
Traditionally, only functions could create local scopes. However, with ECMAScript 6 (ES6), blocks now create scopes. Block means everything that is between curly braces such as the block of an if
statement or a loop. Variables within a block must be declared with the let
or const
keyword as block scope only applies to variables declared with those keywords. Variables declared within a block scope cannot be accessed outside of the block.
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
let num = 2;
console.log(num + arr[i]);
}
console.log(num); // Uncaught ReferenceError: num is not defined...
In the above example, the num
variable declared in the for
loop block cannot be accessed outside the block. It is important to note that with ES6, functions are now block-scoped when in strict mode.
Scope chain and lexical scoping in practice
What is the scope chain? The scope chain concept is quite easy to understand. It is how JavaScript looks up to find variables.
For example, in the code below, the string() function needs access to the age
and firstName
variables. What happens then is that it looks up to its outer scope in search of the variables. It will have access to the age
variable since it is in its parent scope. The question then is whether the string()
function will have access to the firstName
variable. Since the parent scope has access to the firstName
variable, the child scope will also have access to that variable. This is a simple example of how the scope chain works.
// Global scope
const firstName = 'John'
//First scope
const calcAge = function (birthYear) {
const age = 2020 - birthYear;
// second scope
const string = function () {
const result = `${firstName} is ${age} years old and he was born in ${birthYear}.`;
console.log(result);
};
string();
return age;
};
calcAge(2002);
Earlier in this article, you had already seen an example of lexical scoping. Nevertheless, more examples of lexical scoping will better aid your understanding of this concept.
const calcAge = function (birthYear) {
const age = 2020 - birthYear;
if (age >= 18) {
const decade = 1;
var genZ = true;
}
const string = function () {
const result = `John is ${age} years old and he was born in ${birthYear}. John has lived for more than ${decade} decade`;
console.log(result);
};
string();
return age;
};
calcAge(2002);
As you can see from the above code, the string()
function and the if
block statement resides in the scope of the calcAge()
function. The string()
function will have access to the age
variable declared in the parent scope and to the genZ
variable declared in the if
block statement since the keyword var
is not block-scoped. However, it will not have access to the decade
variable since it was declared with the const
keyword which is block scoped. Also, it is important to note that the if
block statement cannot access any variable from the string()
function. All this is a result of lexical scoping. This means that access to variables depends entirely on the variable environment.
Lexical scoping is important in that it prevents conflict of variable names. It resolves this by ensuring that variables are only accessible in the lexical environment in which they are declared. Lexical scoping also makes a concept like closure possible. A closure allows a function to have access to all the variables that existed at its birthplace. Closure is another topic on its own, so this article is not going to delve into it.
Conclusion
In summary, lexical scoping is an important JavaScript concept that allows developers like you to write clean code. This article has given a brief overview of lexical scoping, its types, how it works, and its importance in any program. For more information on scoping, lexical scoping and closures, you can visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures