Research: Context, hoisting, scopes and closures - imkarin/bloktech GitHub Wiki

The JavaScript engine

JavaScript works through the JavaScript engine: a programme that reads and runs the JavaScript code. This is the core of what makes it possible to run your code in web browsers. It does its job in two phases:

  • The (memory) creation phase
  • The execution phase

The Creation Phase

When the JavaScript interpreter reads our code, we enter the creation phase. Several things happen here:

  • A reference to the object that called the function is stored in memory. In the Global Execution context, in a browser, that reference is the window object.
  • The 'this' keyword is also created and stored in memory. It refers to the object that called the function or, in the Global Execution context, the window object.
  • The function's outer environment is created (scope chain).
  • Memory spaces are being made for all variables, functions and arguments. They will all have the initial value of 'undefined'. This is called hoisting, and its advantages will be explained further down this page.

The Execution Phase

After the Creation Phase sets up the execution context, the engine synchronously executed the code within the function, from top to bottom. This phase returns values: it evaluates expressions, returning values, and it executes functions, returning values as well. These values will be assigned to the 'undefined' variables that were stored in the Creation Phase.

Execution contexts

Global Execution context

By default, without writing any js, we have the Global Execution context available; aka. the window, or global object. We can refer to this global object by calling:

  • window (equals to global object)
  • this (equals window)
  • global (equals global object in nodeJS)

Function Execution context

What happens inside the function. The Function Execution context is created whenever a function is called (invoked). This way you can switch between the global and the function execution context. Instead of a global object, an arguments object gets created (array-like).

The scope is the current context of execution, more about this in the section "Scope".

Hoisting

JavaScript's default behaviour is that it moves variable and function declarations to the top of the .js document. This principle is called hoisting. As soon as you declare a variable or function, JavaScript moves it up. This means that you can assign values to variables and call functions even before you declare them, as long as you declare them somewhere in the file.

This code will work:

x = "hello"; 
console.log(x); // Will print "hello"
let x;

This code won't work:

console.log(x); // Uncaught ReferenceError: Cannot access 'aba' before initialization
let x = "hello"

Implementation in my code

To make my code easier to read, I can declare functions at the bottom of the code. I can call these functions earlier in the code, as shown in the images below. The displayContent() function creates list items for every person the user hasn't liked yet (rendered from local storage). The function is called in the beginning of the code, even though it is declared later. This is possible thanks to hoisting.

Hoisting-1 Hoisting-2

Scope

Scope in JavaScript refers to the current context of code. The scope determines which certain variables, functions or objects are accessible right now. If a variable is not in the current scope, it's not available for use. There are two kinds of scopes: global scope and local scope.

Global scope

If a variable is defined outside all functions and curly braces {}, it is defined in the global scope. You can use this variable anywhere in the code, including functions and their inner functions.

Local scope

Variables that are only available in certain areas of your code (locally), are defined in a local scope. There are two types: function scopes and block scopes.

  • Functions scopes: when a variable is declared in a function, it is only accessible in this function (and its inner functions).
  • Block scopes: when a variable is declared with const or let within curly braces {}, it is only accessible within these braces.

Implementation in my code

In the images below, you see how I make use of the global scope in my code. I define variables for the HTML elements in the global scope, so I can use them throughout my entire code (in several functions, that update these variables).

Global variables are being defined, for example the chatList (an <ul> in the HTML document) and the data from localStorage.

Scope1

In the function displayContent, the <ul> is being filled with new content.

Scope2

In the function dislike, the clickedUser is being defined by searching for their name in the data.

Scope3

Closures

Whenever you create a function within another function, you create a closure.

A closure gives you access to an outer function's scope (lexical environment), when you're inside an inner function. They're useful because they let you associate data (the lexical environment) with inner functions that operate on that data.

Every closure has three scopes:

  • Local scope (its own scope)
  • Outer function's scope
  • Global scope

An example of working closures:

// global scope
let e = 10;
function sum(a){
  return function(b){
    return function(c){
      // outer functions scope
      return function(d){
        // local scope
        return a + b + c + d + e;
      }
    }
  }
}

Closures are particularly useful, because they allow you to access a function's private variables from the outside. Here's an example:

function outerFunction() {
  const privateVariable = "Hello";

  return innerFunction() {
    console.log(privateVariable);
  }
}

let showPrivateVariable = outerFunction();
showPrivateVariable(); // console.logs "Hello"

Implementation in my code

I also used closures to improve the readability of my code, and to keep variables private but still accessible from the outside. On every profile page, the user sees a like and dislike button, to rate the person whose profile he's currently looking at. These buttons use the same variable to work: the ratedPerson variable, which is the person whose profile we're looking at.

The ratedPerson's liked status has to be changed to true or false, depending on which button our user clicked. So we have two separate actions, using the same variable. A like function, that gets executed when the like button is clicked, and a dislike function that is bound to the dislike button.

Closure2 Closure1

Sources