JavaScript Theory

This is for those who are theory lovers like myself. Knowing all of this is not necessary to actually be able to write JS code, but it helps to know what happens behind the scenes... not for your actual development but to be able to take abstract and concrete concepts and practice, and be able to code better.

As I said, all these concepts are unnecessary if you know how to code on JS, or if you just want to code and don't care about how the code is processed or how the engines work. I just wanted to get all the concepts for myself and thought, why the hell not, I'll just share the knowledge.

These are my notes, excuse me if I don't make sense, but I make sense to me, and my notes are for me so, I hope you do understand them

image.png

JavaScript is a high-level, prototype-based, object oriented, multi-paradigm, interpreted or just-in-time compiled, dynamic, single-threaded, garbage-collected programming language with first-class functions and a non-blocking event loop concurrency model.

That sounds complicated as hell actually, but if we break down this definition we will actually understand why we picked this definition.

  • High level: Every programm running in a PC needs hardware resource to work. A high-level language does not require the developer to manade the resources manually, where everything happens automatically thanks to something called Abstractions that take that work away from us. The downside is that programs will never be as fast or optimized as low-level languages.
  • Garbage Collected: An algorithm inside the JS engine that remooves all unused objects from the PC memory in order to not clog it up with unnecessary stuff. In a few words, it is our cleaning man.
  • Interpreted or just-in-time compiled: A computers processor speaks in binary, so JS translates the code we write and turns them into binary.
  • Multi Paradigm: Paradigm is an approach and mindset of structuring code, which will direct your coding style and technique, in JS we have Procedural programming (organizing the code in a linear way with function in between).
    There are imperative and declarative Paradigms.
  • Prototyped-based and Object-Oriented: Almost everything in JS is an object (except for primitive values).
  • First-class functions: Functions are treated as regular variables. We can pass functions into functions, and return them from funcitons. It allows us to use several techniques and to use functional programming.
  • Dynamic: It is dynamicly typed. We don't assign data types to variables. They only become known at runtime. The type of variable can easily change as we re-assign variables.
  • Single-threaded & Non-blocking event Loop concurrency model: Wait, I'll tell you what this is at the end of this post.

The JS Engine

A JS engine is a computer program that executes JS code. Every browser has its own engine, but the most popular one is Google's V8, but also Node.js is a very popular one.
The components of an engine contains:
Call stack - Where our code is executed using an execution context.
Heap - An unstructured memory pool that stores all the objects that our application needs.

The way that code is compiled to machine code so that it can actually be executed afterwards.

Compilation vs Interpretation

A computers processor only understands binary, and JS achieves the conversion from Javascript code to binary through these two.
Compilation - The entire source code is converted into machine code at once. First the machine code is built, and then it is executed in the CPU. This execution can happen at any time after the compilation.
Interpretation - There is an interpreter that runs through the source code and executes it line by line. The code is read and executed at the same time. The source code also needs to be converted into machine code but it happens when it is executed.

JS used to be only interpreted, but intepreted languages are much slower than compiled, so JS adapted to this situation and modern JS uses a mix between these two, called just-in-time (the entire code is convertes into machine code at once, then executed immediately). We still have the compilation process but there is no portable file to execute, so the execution happens immediately after the compilation.

The process goes as follows:
The code is parsed into an AST (Abstract syntax tree) which consists of spliting each line of code into pieces that are meaningful to the language (like keywords) and saving these pieces into the tree in a structured way. This step also checks for syntax errors. This tree will be used in the future to generate the machine code.
The code is compiled, taking the generated AST and compiles it into machine code. This machien code then gets executed right away (with the just-in-time compilation process), which happens in the Call Stack.
The code is being optimized while the program is being executed, and it can happen multiple times during the process. This process is what makes modern engines so fast. This happens in special threads that we can't access from the code.

JavaScript Runtime

Runtime in the browser: Imagine a big box that contains all the things we need to use JS in the browser:
It contains the basic parts of the engine, which are the Heap and the Call Stack.
An engine is not enough, we also need web APIs (everything related to the DOM, timers, Fetch API and much more) which are functionalities provided to the engine that are not part of the JS language itself, JS only gets access to this API through the global window object. These functionalities are not part of JS but are part of the Runtime.
Callback Queue: This is a data structure that contains all the callback functions that are ready to be executed such as events like click, timer, data and much more. As the event happens, the callback function will be called (The first thing that happens after the event, the call is placed in the callback Queue, when the call stack is empty, the callback function is passed to the stack so that it can be executed thanks to something called event loop, which takes callback functions from the callback queue and puts them in the callback stack so they can be executed).

This is for browsers, but JS can exist outside the browsers, but I'm not gonna get into that as I have no knowledge about that.

Execution contexts and the Call Stack

Let's assume that our code was just compiled and is ready to be executed.

A global execution context is created for the top-level code (code that is outside of functions), because the functions will only run when they are called.

An execution context is an environment in which a piece of JS is executed, like a box that stores all the necessary info we need to run a code. JS always runs inside an execution context.

In any JS project, there is only one global execution context and it is as the default context.

After the execution context is created, it will execute the top-level code.

Once the top-level code is executed, then the functions will begin to execute (waiting for callbacks). For each function an execution context will be created depending on the data and information each function needs to be executed. All these EC per function make up the call stack.

An execution context is made of:
Variable environment: All the variables are stored here, also the special arguments object (all the arguments passed into the funciton that the EC beloings to).
Scope chain: Consists of references to variables that are out of the current function. To keep track of the scope chain, it is stored in each execution context.
This Keyword: We'll jump into this below.

These three are generated in something called Creation phase which happens right before the execution.

Note: Execution contexts belonging to arrow functions do not get arguments object nor this keyword and will get them from the closest regular function parent.

Scope

Each execution context has a variable environment, a scope chain, and the .this keyword.

Scope is how our program's variables are organized and accessed, which means where are our variables and how and where can we access them.

In JS there is something called Lexical scope that is basically the control of the variable scoping by the placement of functions and blocks in the program's code.

Scope is the space or environment in which a certain variable is declared. There is global scope, function scope, and block scope.
Global scope: It is for top-level code, outside of any function or block and it is accessible from everywhere in the code.
Function scope: Each variable declared inside the function scope are only accessible inside that function. It is also known as local scope.
Block scope (ES6): Before, only functions created scopes but since ES6 was introduced, now blocks also create scopes. Blocks are everything between curly braces. Variables created inside a block are only accessible inside the block, not outside. Only let and const variables are restricted to the block where they are created, (var is still accesible outside the block). let and const are block-scoped and var is function-scoped. Var ignores blocks. When we code in strict mode (highly recommended), functions declared inside a block are also block-scoped.

The scope of a variable is the region of our code where a certain variable can be accessed.

The Scope Chain

Take the code below as example for the Scope Chain.

const anomadsoul = "Eric";
 function first() {
     const age = 32;

     if (age >= 30) {
         const decade = 3;
         var millenial =  true;
     }

     function second() {
         const favCoin= "Hive";

         console.log(`${anomadsoul} is a ${age} old and loves ${favCoin}`);
     }
 
     second ();
    }
    first();

The Global Scope: The only declarations in the global function is "anomadsoul" and the "first" function.
Functions scope: The first scope despite being in the global scope, creates a function scope, and inside that first function there is also a second scope created by the second function (A nested scope).

If you notice, the second function is calling variables declared outside of the function scope (anomadsoul and age) so, how is it possible that we can call them? Well, every scope has access to the variables of its parents' scopes, so the second function can access the first function's scope and the global scope (the same applies to function arguments). If a function is looking for a variable and it can't find it in the current scope, it will look in the parent's scope, and so on...

...that is the Scope Chain.

It is worth noting that parent scopes have no way of getting access into a child's scope.

But we forgot about Block scopes in that example... the if block scope between the curly braces, does not have access to its sibling block second and viceversa. If we tried to call decade inside the second function, we wouldn't be able to do so, and it is all thanks to lexical scoping.

Scope Chain vs Call Stack

Take the next code as example:

const a = "anomadsoul";

function first() {
    const b = "HiveUser";
    second();

    function second() {
        const c = "DogOwner";
        third();
    }
}

function third() {
    const d = "FootballPlayer";
    console.log(d + c + b + a);
}

We start by calling the third function, which calls the second, which calls the first. The call stack will look like this:

Third on top of second, which is on top of first, which is on top of the Global EC, and the call stack will start running the functions from top to bottom.

The scope chain (the order in which the functions are written in the code) has nothing to do with the order in which functions are called.

The third function can't reach the second and first functions' variables due to scope chain, so this code won't work.



This is enough for today, I still have like one or maybe two parts left, but the post was already getting out of hand here.

As I said, I hope you understand my notes and are able to get something out of it.

H2
H3
H4
3 columns
2 columns
1 column
6 Comments
Ecency