Hoisting

In JavaScript hoisting refers to the process by which the compiler moves the declaration of functions, variables or classes up their scope, before the code is executed...

What is a scope?

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
js

The global scope, the accessible one

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.

The local scope, the hold

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
js

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
js
function scoped() {
  var scope = "local" 
}
console.log(scope) // ❌ Uncaught ReferenceError: scope is not defined

if (true) {
  var scope = "local";
}
console.log(scope) // local
js

The lexical scope, the nerdy?

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"
js

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).

The how, the closure

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"
js

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"
js

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:

  1. Check if cat and dog are called in the local scope of childFunction, otherwise look for them up the chain (back to the parent function's scope).
  2. Look for cat and dog inside the local scope of his parent, here parentFunction, the first one is found, perfect, we can print Jerry, but the other dog can't be found so we have to go back up the chain again.
  3. Look for dog in the global scope, and here, Tom is finally printed.

This concept is called scope chain, going from one scope to another in order to find what needs to be stored for later use.

Conclusion

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.

Last updated: October 22, 2022

⚡ Who am i to talk about this? ⚡

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: