JS Scope Theory

What's a block?

According to the ECMAScript Language Specification, a block statement is composed by a pair of brackets and a list of statements. If you are familiar with functions or any sort of flow control statement (for loops, if statements, switch statements...), you are already familiar with blocks.

function addOdds(...numbers) { // 1st block start
      var total = 0;
      for (let number of numbers) { // 2nd block start
        if (number % 2 !== 0) { // 3rd block start
          total += number;
        } // 3rd block end
      } // 2nd block end
      return total;
    } // 1st block end

Blocks allows us to group a set of statements together.

What's the scope?

The scope can be seen as the set of rules which determines whether a variable is available or not in a specific execution context or region of the code.

JS Scope is lexically determined.

This means that the scopes are set before the code execution, during the compilation (lexical comes from the compilation lexing step where the code is split into tokens, the smallest meaningful chunks of information).

We can take advantage of this information in order to predict and visualize the scopes just from looking at our code!

How do we visualize the scope chain?

The JS engine declares a scope for each block in the code and assigns each variable to the scope where it was declared.

In order to predict scopes, thus, we will have to think like the JS engine does! Every time we find a block in our code, we will assign a color to it. Every time we find a variable, we will also assign a color to it. But in order to do that, we need to dive into some concepts!

Variables: target and source references

Every variable in our program has one out of two roles: target or source.

In technical documents, target is commonly referred as LHS (left-hand side) and source is commonly referred as RHS (right-hand side). This terminology was adapted by computer scientists from maths.

Whenever we assign something to a variable, we say the variable is in a target reference position (or LHS): the variable is the target of the assignment operator.

In other words, a source reference to a variable has a writing role: we write a value into the variable.

Whenever we pass a variable as an argument in a function call, we say the variable is in a source reference position (or RHS): our variable is the source of the value for the function parameter.

In other words, a target reference to a variable has a reading role: we read the value that the variable holds.

var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // "array" is a target reference
    addOdds(...array); // "array" is a source reference

To state it (clearly):

  • a target reference is the one where we access a memory address to write a value in it, and
  • a source reference is the one where we access a memory address to read a value from it.

Variable declarations

A variable declaration is a statement that allocates some space in memory to store a value.

Formal variable declarations can be easily spotted: in JS, there are 3 keywords which will help us to formally declare a variable: var, const and let.

However, there are other variables in the target roles that might join our variable declaration group, as in function parameters or loop declarations.

function add(a, b) {
    /* 
      a and b fulfill the target role and are indeed variable declarations
      which will be initialized with the passed arguments at run-time
    */
      return a + b;
    }

Scope & Variables

The variables declared in a particular scope will only be available inside that scope and the nested ones.

To understand JS scoping rules applied to variables availability, we need to differentiate between function scoped variables and block scoped variables.

Before ES2015, the only way to declare a variable in JS was via the varvariable declaration. The var variable declaration declares a function scoped variable. Function scoped variables set the bounds for their availability to the nearest enclosing function and, as we discussed previously, the nested blocks.

ES2015 introduced let and const variable declarations. Both let and const variable declarations declare block scoped variables. Block scoped variables set the bounds for their availability to the nearest enclosing block (whether it is a function, a pair of brackets, an if statement, a for statement...), and, as we discussed previously, the nested blocks.

Conclusion

The information we need to extract from the previous sub-Sections and which will give us the rules to assign a color to our variables can be summarized in two main ideas:
— Variable declarations will always have the color of the block where they are being declared, attending to the differences that using block or function scoped variables might carry. We will always consider function parameters as declarations inside the function scope.
— Whenever we find a variable in a source position or a variable in a target position which is not a variable declaration, we will perform a lookup for its declaration, starting in the current block all the way up to the outermost. We will stop as soon as we find it and will assign to it the color of the block where it was declared.

With our current knowledge, we should be ready to test our new gained scope analysis skills!
Try revisiting our addOdds function and assign a colored bubble to each scope and variable we can find!

Solution
// global/outer scope start: WHITE
    function addOdds(...numbers) { // 1st block start
      var total = 0;
      // function scope: RED
      for (let number of numbers) { // 2nd block start
        // loop scope: BLUE
        if (number % 2 !== 0) { // 3rd block start
          // if scope: GREEN
          total += number;
        } // 3rd block end
      } // 2nd block end
      return total;
    } // 1st block end

About this project

Bubbl.es is the first of the «3 pilars project» inspired by Kyle Simpson. It represents the scope chain as colored bubbles. The main purpose of this project is helping new JS students to understand the scoping system and is intended for small snippets; however, it should be able to handle any script.

Authored by Marcos NASA G (@MarcosNASAG) - 2020.

GitHub icon

Check it in GitHub

Want to learn more?

You Don't Know JS Yet is a book series by Kyle Simpson (@getify) which dives deep into the core mechanisms of the JavaScript language.

The second chapter of the series (Scope & Closures) takes a look into how the scoping rules behave in JS.