More on the JavaScript interpreter

  • Last updated on 9th Jul 2022

Introduction

The JavaScript stack is a data structure that allows for data to be stored and accessed in a last-in, first-out (LIFO) manner. In other words, the data that is stored last in the stack will be the first data that is accessed when the stack is used. This is in contrast to a data structure like a queue, which allows for data to be stored and accessed in a first-in, first-out (FIFO) manner.

Execution Context and Call Stack overview

The execution context is the set of conditions under which the code block is executed. How this relates to JavaScript would be with the interpreter going through one line of code at a time. In order to keep track of where it is in the code, and what variables are currently available, it uses the stack. Another way to put it: the stack is a data structure that allows items to be added or removed in a particular order. For example, when a function is called, the interpreter will push the current line of code onto the stack, and then execute the function. If this repeats several times, like a statement needing data from another function, the data structure “stacks”. Once the function has finished executing, the interpreter will pop the current line of code off the stack, and continue executing the next line of code. If an error occurs, the interpreter will unwind the stack, and execute the previous line of code.

Call stack example

function add(a, b) {
  return a + b
}

function average(a, b) {
  return add(a, b) / 2
}

let x = average(10, 20)

You’re able to see that the calls to each function “stack” on top of each other. Once they’re finished running, then they’re popped out of the call stack. Stack overflow can happen.

img

Hoisting

For example, the execution context for a function includes the variables that are in scope for that function. The execution context for a global script includes the global variables that are in scope for that script.

Hoisting is the process of moving declarations to the top of their scope. In JavaScript, this means that variable and function declarations are moved to the top of the current execution context. This can be confusing for developers who are not familiar with the concept, as it can lead to code that behaves in unexpected ways. Scope is the set of variables that are accessible to a piece of code. In JavaScript, there are two types of scope: global scope and local scope. I’ve explained these before in my previous article.

Execution Context & Hoisting

Whenever a script enters a new execution context, it’s actively being split into two different stages for the activity.

Prepare

(1) The new scope is created. (2) Functions, variables, and arguments are created. (3) The value of the this keyword is created as well (that’s how we can bind stuff).

Execute

(1) Values can be assigned to variables. (2) Referencing functions and running code. (3) Execute statements.

We just call these two stages hoisting everything to the top of the global execution context in our Browser Object Model (BOM). Variables Objects are also created when hoisting. This in essence explains why we’re able to call functions before they’ve been declared (with function declaration i.e someFunction()_) and assign some values to a variable that hasn’t been declared yet in our script (everywhere, mostly haha).

So when a script is loaded, all the variables and functions are “hoisted” to the top of the script. This means that they are available to be used anywhere in the script, even before they are declared.

Hoisting is the reason why this works (in the interpreter’s eyes):

let pizzaTime = getPizza();

function getPizza() {
    returnplay the tune;
}

Scope in-depth

Scope refers to the visibility of variables. There are two types of scope: global and local. Functions in Javascript have lexical scope (fancy for they’re linked to the object they’re defined within). This allows us to get functions or variables from the outer scope unless otherwise specified.

Global variables are visible throughout the script. Local variables are only visible within the function where they are defined. JavaScript also has a mechanism for creating closures. A closure is a function that has access to its parent’s variables. Closures are used for a variety of purposes, including keeping variables from being garbage collected. When a function is invoked, an execution context is created.

This execution context has a variable object that contains the variables, functions, and parameters available within it. The execution context also has a reference to its parent’s execution context. This allows the function to access variables from its parent’s execution context. If I had a dollar for every time I’ve written the phrase “execution context,” I would have enough money to buy a coffee.

Example

Below we can access our global variable x from within our function add.

const x = 'pizza time multiplier!!!'

pizzaTimeX2() {
// We can call pizzaTimeX2 because of hoisting
}

function pizzaTimeX2() {
  console.log('Inside function')
  console.log(x)
}

console.log('Outside function')
console.log(x)
// We can access console x because it’s part of the global execution context.

Ending Thoughts

JavaScript’s scope is a bit different from other languages. It is important to understand how scope works in JavaScript in order to avoid any potential errors. Hope this helped!

Resources:

Call Stack

Scopes