DEV Community

Aarti
Aarti

Posted on

Advanced Js recap [part 1]

A quick read, to brush up javascript concepts. Read through this if it has been a while and you need a refresher of some of the js basics.
In this part, I will be starting with the execution context and how it relates to scope, closures, hoisting and 'this'.

The execution context is an environment in which the code is executed. All code runs in some execution context.

  • The global execution context is created when code starts executing.
  • A function execution context is created when a function call is encountered. It is stacked on top of previous ones and removed once its execution is completed.

Phases of execution context

An execution context has 2 phases - creation and execution.

  • The creation phase is the first parse of function code. This includes creation of scope, scope chain and determining the value of this. Each of these have been elaborated below.
  • The execution phase involves assigning variables and running the code. If a function call is found, a new execution context is created.

The lexical environment/scope variables

Lexical environment depends on where the code is written i.e.
the location of the words as opposed to where the function is called. It determines the available/accessible variables for the function.

  • During creation, memory is allocated for variables, functions and arguments in the lexical environment.
  • This leads to hoisting of variables(var declarations) and functions. Variables are partially hoisted as memory is allocated but value is only assigned during execution.

Hoisting is the action of moving all variable and function declarations to the top of their existing scope. It makes code unpredictable and can be avoided by using IIFE for functions and let/const for variables.
Note: Vars are function scoped, let and const are block scoped

Here is the classic example of setTimeout yielding unexpected results due to hoisting and how let or IIFE can fix it.

/* Below loop prints '5' every time console.log is executed. 
This is because i is hoisted and gets assigned value 5 
before the callbacks of setTimeout execute */ 
for (var i = 1; i < 5; i++) {
  setTimeout(() => console.log(i), 0)
}
// Fix1: declaring i as let which keeps i within block scope
for (let i = 1; i < 5; i++) {
  setTimeout(() => console.log(i), 0)
}
/* Fix2: using an IIFE for setTimeout that explicitly passes i 
to keep the correct value in scope */
for (var i = 1; i < 5; i++) {
  (function (i) {
    setTimeout(() => console.log(i), 0)
  })(i)
}

Scope chain

A scope chain is a link to the function's parent environment variables. This enables closures to form.

A closure is created when a variable is accessed from anywhere up the scope chain. It is kept in memory even if it leaves the enclosing scope. Some common uses of closures:

  • Avoid redeclaring large variables
  • Encapsulation => restrict access, have private variables
  • Creating singletons - single instance of an object

Determining the value of this (a.k.a. context)

'This' refers to the object which has the function as a property and calls the function.
eg. obj.sayHi() - this in sayHi function would be obj

  • The value of this depends on how the function was called (dynamic scope) and not where it was written (lexical scope)
  • Only Arrow functions have lexically bound this. Before arrow functions were added to js, either the function bind method was used or a variable self was assigned to this to get lexical bind.
  • Context is object based while Scope is function/block based

Uses of this in objects

  • gives methods access to their object
  • Avoid repetition by executing same function for multiple objects

The value of this can be modified by function properties call, apply and bind.

Call and apply can modify the this of existing methods on objects. The only difference between them is call takes separate arguments while apply takes argument array. Bind can be used to return a new function with a custom this value.
Eg. In the code below, this in sayHi function would be newObj rather than obj

const newObj = {a: 1};
obj.sayHi.call(newObj);

An example

Here's a simple example of what the execution context would look like for the below code.

//global execution context created
callSayHello();  //new function execution context created
var name = "Bob" //global variable hoisted in creation phase of global execution context and assigned on execution
function callSayHello() {//global func hoisted
  sayHello(); //new function execution context created
}
function sayHello() {//global func hoisted
  console.log( `Hello there ${name}`); //closure created
}

Alt Text


Thanks for reading my first ever post :)
Hope this served as a useful reminder of these concepts. If you want to read about them in further detail, I liked the js demystified series from codeburst.

Top comments (0)