Advanced JavaScript Course Notes pt.2

Advanced JavaScript Course Notes pt.2

Dive deep into JavaScript, and Become one of the best 10% JavaScript developers around the world.

Hi there! Before reading this article, you should understand some concepts that are defined in the first part of this Advanced JavaScript Course Notes series.

A JavaScript Runtime is where all the JavaScript needed pieces are brought together. The heart of every JavaScript Runtime is the JS Engine. But the Engine alone is not enough!

Let's take the browser for example, which is the most common JS Runtime. It has a JS Engine, but to work properly, we also need access to the web APIs (DOM, Fetch API...etc). A typical JavaScript runtime also includes a CallBack Queue.

Blog Ressource 1.png

  • The Callback Queue is a data structure that contains all the Callback Functions that are ready to get executed.
  • The Callback Functions are passed to the Call Stack inside the JS Engine from the Callback Queue by the so-called Event Loop

Blog Ressource 2.png

! Keep in mind that JavaScript can exist outside the browser, there are other JS Runtimes, like NodeJS; it's like the browser runtime, but instead of web APIs, we have multiple C++ Bindings and a so-called Thread Pool.

Execution Context

Now, it's time to understand how the Compiled AST (Abstract Syntax Tree) is executed:

  • Step 1: Creation of Global Execution Context (GEC) (for top-level code that's not inside a function or method)
  • Step 2: Execution of the top-level code (inside the GEC)
  • Step 3: Execution of functions and waiting for Callbacks

Execution Context (EC): It's an environment in which a piece of JS code is executed. It stores all the necessary information for some code to get executed.

! For each function call, a new execution context is created, and all the execution contexts make up the Call Stack.

When all the functions are done executing, the Engine will keep waiting for the Callback functions to arrive, so that it can execute them.

-> What's inside the EC?

1- Variable Environment:

  • Let, Const, and Var declarations.
  • Functions.
  • Arguments Object.

2- Scope Chain: Consists of references to variables that are located outside of the current function.

3- a Special variable called the this keyword.

All of those three parts are generated during the "creation phase" right before execution.

Blog Ressource 3.png

JS Engine the Call Stack

A regular JavaScript program contains hundreds of functions, that's why the engine needs the Call Stack to keep track of the order in which the functions were called.

The Call Stack is where the ECs get stacked on top of each other to keep track of where we are in the program's execution. So basically, the execution context that's on top of the stack is the one that is currently running, and when it's finished running, it will get removed from the stack so the one below it will get executed until it finishes running.

Scope and Scope Chain

-> Important concepts:

  • Scoping: it's how our program's variables are organized and accessed by the engine.
  • Lexical Scoping: it means that the scoping is controlled by the placement of functions and blocks in the code.
  • Scope: it's a space or environment in which a certain variable is declared. There's Global Scope, Function Scope, and Block Scope.
  • Scope of a variable: it's a region of the code where a certain variable can be accessed.

-> The 3 types of scope:

  • Global Scope: Top-level variables, outside of any function or block.
  • Function Scope (Local Scope): Variables are accessible inside the function where they were declared, and also its nested functions and blocks but nowhere else.
  • Block Scope: "By Block we mean the lines of code inside curly braces { } like the for loops and if conditions". In block scope, variables are only accessible inside the block. However, this only applies to "let" and "const" declarations.

! In strict mode, functions are also block-scoped

Note that "let" and "const" are block-scoped, while "var" is function scoped.

-> Scope Chain: It means that an inner scope has access to all the variables declared in outer scopes.

Hoisting

-> Many people think that "Hoisting" makes some types of variables accessible/usable before they are declared.

-> What really "Hoisting" means, is before execution, the code is scanned for variable declaration, for each variable, a new property is created in the variable environment object.

! Keep in mind that not all the variable types are "Hoisted"

Blog Ressource 4.png

-> The Temporal Dead Zone (TDZ) It's the region of the scope in which the variable is defined, but can't be used in any way. "If we try to access a variable in the TDZ we will get a Reference Error that says (cannot access 'variable name' before initialization), Unlike when trying to access a variable that was never declared, we get a different error that says ('variable name' is not defined)

-> Why do we need the TDZ? It makes it easier for developers to avoid and catch errors and bugs!

the this keyword

It's a special variable that is created for every execution context. It takes the value of the "Owner" of the function in which the this keyword is used.

-> this is not static. It depends on how the function is called, and its value is only assigned when the function is called.

Blog Ressource 5.png ! We can manipulate the "this" keyword by using different ways to call functions like new, call, apply, and bind.

Primitives vs. Reference types (objects)

  • Primitive types: Number, String, Boolean, undefined, null, Symbol, BigInt. ! Stored in the EC (Execution Context) within the Call Stack of the JS Engine.

  • Reference types: Object literals, Arrays, Functions, Maps, Sets...and more ! Stored in the memory Heap of the JS Engine.

-> How Primitives are stored in the memory? When a primitive is declared, JavaScipt creates a so-called "unique identifier" with the variable name, then a piece of memory will be allocated with a certain address, and finally, the value will be stored in the memory at the specified address. We should keep in mind that the identifier points to the address where the value is stored, not the value itself. Blog Ressource 6.png

When we declare a new primitive and assign an existing primitive to it, they both will be pointing to the same memory address.

Important Note: the values at a certain address are immutable! "They cannot be changed", so what happens when we assign a new value to a variable is that the identifier will no longer point to the current memory address, instead, a new piece of memory will be allocated to a new address, and our variable points to that new address.

-> How Reference type is stored in the memory?

When it's created, it's stored in the Heap under a certain memory address. But in this case, the identifier doesn't point directly to the Heap memory address where the object is stored. Instead, a new piece of memory in the Execution Context is allocated with a new address, and the identifier points to that address in the EC which contains the Heap address of the object as a value. Blog Ressource 7.png

It works this way because objects might be too large to be stored in the Call Stack. Instead, they're stored in the Heap, which is like an unlimited memory pool. and the EC stores only the address to where the object is stored in the Heap so that it can find it when necessary.

Recap

Until now, we have learned about the JavaScript Runtime, JS Engine, and how JavaScript stores primitive and reference types!

Stay tuned for the next article where we will be talking about the JavaScript Data Structure!

If you reached this line, then congrats! You're on the right way to becoming one of the best 10% JavaScript developers in the world.