Let's start by answering the most important question, what is a scope? In JavaScript, a scope refers to the current execution context in which expressions are defined or can be referenced. I like to think of scope as layers where each layer has its own context that determines the accessibility of variables, functions and classes, let's see an example:
const rootLayer = 0;
{
const layer = 1;
console.log(rootLayer) // 0
console.log(layer) // 1
}
console.log(rootLayer) // 0
console.log(layer) // ❌ Uncaught ReferenceError: layer is not defined
When you start writing code at the root of any given file, you are in the global scope, there, if you define a variable, it will be accessible everywhere in your project. In the previous example the rootLayer variable is accessible everywhere including inside the block (represented by braces ), contrary to the layer variable which is defined inside the block.
If you assign a variable inside a function (or any block for that matter) the declaration will happen in the local scope of that function so it is only accessible inside it.
function scoped() {
const scope = "local";
console.log(scope) // local
}
console.log(scope) // ❌ Uncaught ReferenceError: scope is not defined
The same goes for blocks, if you assign a variable inside a conditional statement for example, it will only be accessible inside the block.
if (true) {
const scope = "local";
console.log(scope) // local
}
console.log(scope) // ❌ Uncaught ReferenceError: scope is not defined
Note that: with var, the scope of the variable is its current execution context which is either:
Otherwise just don't use var and you'll be fine.
function scoped() {
var scope = "local"
}
console.log(scope) // ❌ Uncaught ReferenceError: scope is not defined
if (true) {
var scope = "local";
}
console.log(scope) // local
The lexical scope is the definition area of an expression. Also called static scope, think of it as the place where it was defined, not to be confused with the place where the expression is invoked (or called).
const dog = "Tom";
const parentFunction = () => {
// Layer 1
const cat = "Jerry"; // Lexical scope
const childFunction = () => {
// Layer 2
console.log(cat);
console.log(dog);
}
return childFunction();
}
parentFunction();
// "Jerry"
// "Tom"
As you can see, in the childFunction, we get to call cat and dog without errors but how, since they're both defined outside the block?
Well that's exactly what the lexical scoping does, inside the inner scope you can access variables of outer scopes. (It's called lexical (or static) because the engine determines (at lexing time) the nesting of scopes just by parsing JavaScript source code, without executing it).
Okay, so now we know about the lexical scope allowing us to access variables from outer scopes, but how does it work exactly?
Let's get back to our previous example:
const dog = "Tom";
const parentFunction = () => {
// Layer 1
const cat = "Jerry";
const childFunction = () => {
// Layer 2
console.log(cat);
console.log(dog);
}
return childFunction();
}
parentFunction();
// "Jerry"
// "Tom"
Inside childFunction() we get to call cat and dog but did you notice that childFunction() was called inside the lexical scope of parentFunction()?
Would it work if we instead did something like this:
const dog = "Tom";
const parentFunction = () => {
// Layer 1
const cat = "Jerry";
const childFunction = () => {
// Layer 2
console.log(cat);
console.log(dog);
}
return childFunction();
}
function run() {
const myParentFunction = parentFunction();
myParentFunction()
}
run();
// "Jerry"
// "Tom"
Now childFunction() is executed outside the lexical scope of parentFunction() but inside the run() . For this to work childFunction has to "remember" cat and dog from its lexical scope, the place where it was defined.
So there it is the final part, closure which is the ability of a function to "remember" and access its lexical scope regardless of where it's executed.
Thanks to closure the compiler does this:
This concept is called scope chain, going from one scope to another in order to find what needs to be stored for later use.
hoisting and closure allows functions, variables and classes to be used safely in the code (limited to their lexical scope) before being declared.
Note that it is generally not recommended to define everything explicitly at the top of the file, as this can lead to unexpected errors and memory as the engine cannot decide whether this variable should be garbage collected or not.
Understanding the concept of scope and hoisting is crucial to writing efficient and effective code aswell as help you write better code and avoid common pitfalls when working with JavaScript.
Honestly i am no one, i've just been coding for 3 years now and i like to document every solutions to every problem i encounter. Extracting as much code snippets and tutorials i can so that if i ever need it again i just have to pop back here and get a quick refresher.
Feel free to me through this learning journey by providing any feedback and if you wanna support me: