Scope, Closure and Lexical Environment
Scope, Closure and Lexical Environment
When writing JavaScript code, you often notice that some variables are "accessible" while others simply throw errors. Why does this happen? What determines where a variable is accessible? The answer lies in these three key concepts: Scope, Closure, and Lexical Environment.
In this article, I’ll explain everything in a very simple way with examples. Understanding this will help you write more precise and secure code without fearing "ReferenceError"s.
Scope (միջավայր) refers to where a variable is visible or accessible. In other words, JavaScript determines whether you can use a particular variable in a given section of code or not.
There are three main types of scopes:
// Global Scope
let globalVar = "Hello";
function sayHi() {
// Function Scope
let name = "Anna";
console.log(globalVar); // accessible
console.log(name); // accessible
}
console.log(name); // Error: name is not defined
let and const are block-scoped, while var is only function-scoped.
if (true) {
let x = 10;
var y = 20;
}
console.log(x); // Error: x is not defined
console.log(y); // 20
This distinction is important for understanding when a variable "lives" and when it "dies."
Lexical Environment is a fancy term that essentially means where your code is written. JavaScript determines variable accessibility based on where you wrote them—**not where you called them from**.
function outer() {
let name = "Armen";
function inner() {
console.log(name);
}
inner();
}
outer(); // will print "Armen"
In this example, the `inner()` function can use the `name` variable because it was written inside it. This is the essence of "lexical environment." If a function is written in a certain environment, it remembers that environment’s variables.
A closure is when a function "remembers" its outer environment’s variables—even when that function is called from somewhere else.
function outer() {
let counter = 0;
return function inner() {
counter++;
console.log("Counter:", counter);
};
}
const count = outer();
count(); // Counter: 1
count(); // Counter: 2
This is the magic of closures. Even though `outer()` has already finished executing, the `inner()` function defined inside it **still has access to `counter`**. The closure "closes over" the outer values.
This is why closures are often used to create private variables.
function createUser(name) {
let password = "secret123";
return {
getName: function () {
return name;
},
checkPassword: function (guess) {
return guess === password;
}
};
}
const user = createUser("Ani");
console.log(user.getName()); // Ani
console.log(user.checkPassword("wrong")); // false
console.log(user.checkPassword("secret123")); // true
Here, the `password` variable is accessible only via closure—it cannot be modified from the outside. This is similar to having a private field in a class.
In JavaScript, scope determines where a variable is accessible, lexical environment determines the environment in which a function was created, and closure allows a function to "remember" that environment regardless of where it is called from.
If you master these concepts, you’ll write code with more confidence, debug more effectively, and build cleaner projects.