Get an in-depth view of Scopes in JavaScript

Get an in-depth view of Scopes in JavaScript

Understand the scope, its types, scope chaining, lexical scope, and lexical environment in JavaScript

·

6 min read

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.

https://media.giphy.com/media/120kDB2lOrYnV6/giphy.gif

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.

https://media.giphy.com/media/l3q2EOu4nu1D8uJKU/giphy.gif

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?

https://media.giphy.com/media/xYEYXCt93QZTP5adXQ/giphy.gif

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.

https://media.giphy.com/media/sXDM5i3nTaOxHEyGk8/giphy.gif