Exploring the Fundamentals of JavaScript Engine
Diving into the Core Principles of How JavaScript Engine Operates
JavaScript is a single-threaded language because while running code on a single thread, it can be really easy to implement as we don’t have to deal with the complicated scenarios that arise in the multi-threaded environment like deadlock. Since JavaScript is a single-threaded language, it is synchronous. By being single-threaded it has a single calling stack.
Single thread means that has only one call stack. Stack overlow and when calling stack is full.
Babel is the compiler that takes ES6 code and translates it to ES5.
JS Runtime
Memory heap
A large region in the memory of the JS engine where data can be stocked.
Global variables are bad because they can fill up the memory heap and can be unused. This will result in memory heap.
Call Stack
This is where all your javascript code gets pushed and executed one by one as the interpreter reads your program, and gets popped out once the execution is done.
Event Loop
JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks.
Callback Queue
Here is where asynchronous codes get pushed to and wait to be pushed by the event loop in the call stack.
Order of Execution Between Microtasks and Macrotasks
- JavaScript’s event loop first processes the main code (synchronous code).
- After the synchronous code completes, it processes all microtasks in the microtask queue before picking the next macrotask.
- The cycle continues: execute all microtasks ( fetch ) , then a macrotask (timeout), and so on.
Context
Execution Context
For each function called it’s created an execution context. Every time we run code is run in the execution context.
Global Execution Context
The last execution context is the Global Execution Context. Global execution context is formed of Global Object (in the browser is window) and this keyword.
Javascript Engine in the browser creates Global Execution Context.
Lexical Environment
Execution context is telling which Lexical environment is currently running. Lexical scope determines our available variables depending on where we call our functions. First lexical environment is global environment.
Hoisting
During the compilation phase, a function declaration is going to the top of the global execution context. Javascript analyzes what function or variable are created and allocate memory for them. Variables are partially hoisted, and functions are fully hoisted. It looks for the “var” or “function” keyword. It won’t work for “let” or “const”.
This means that if you try to access a variable declared with var
before its initialization, it will exist but will be undefined
.
Functions
Definition: a set of statements that performs a task or calculates a value, but for a procedure to qualify as a function, it should take some input and return an output where there is some obvious relationship between the input and the output. Parameters are function variables.
Functions expression ➡ var canada= function(){}
Function declaration (get hoisted ) ➡ function india(){}
Function execution ➡ india()
Function arguments are the real values passed to (and received by) the function
Function argument ➡ arguments keyword helps to get all arguments from a function. Should be used as followed console.log(Array.from(arguments)) 😀.
Pure function is a function that has no side effects, and if you give a input will return the same output. Side effects means if the function modifies anything outside the function e.g. axios calls, mutation in an array, console.logs(because it has an effect the document). Are easy to test and to compose, and predictable.
- A function’s pure if it’s free from side effects and returns the same output, given the same input.
- Side-effects include: mutating input, HTTP calls, writing to disk, and printing to the screen.
Perfect functions should :
- do 1 task have a return statement
- be pure, no shared state
- immutable state(return a new copy of the global state)
- composable, predictable.
Imperative vs Declarative
Imperative code code that tells the machine what to do and how to do it. Machine code is imperative. A for loop is more imperative.
Declarative what to do, and what should happen. Higher level language is more declarative, we don’t worry about memory allocation and so on.
Variable Environment / Variable context → context of each function
Scope
4 types of scope in JS: Block Scope, Global Scope, Function Scope, and Module Scope.
A variable declared outside a function has Global Scope.
Function Scope vs Block Scope
Function scope refers to scope that is accessible only in functions.
To use Block Scope ( inside if statement / for loops). Block scoping means declaring a variable inside curly brackets. — let — gives opportunity to have block scope.
Scope Chain — all functions have a global lexical environment, hence this allows us to access global variables. Lexical scope determines what variables are available. Functions that have other functions as return share variables of their parents.
⚠ var — it available outside block scope (outside block meaning outside {})
Global Variables — the main issue with these variables is that someone on another component may overwrite these variables.
IIFE - immediately invoked function expression. Can be called immediately after. It is an option to use function scope.
(function (){})()
Event propagation is the mechanism by which events in the Document Object Model (DOM) are processed. The DOM represents a hierarchical tree-like structure of nodes, where an event that occurs on one node can propagate to its parent, grandparent, and other ancestor nodes.
In JavaScript, there are two main event propagation methods: bubbling and capturing.
Bubbling: In the bubbling model, an event starts at the most deeply nested element and propagates up through its ancestors. For example, if a user clicks on a button, the click event would first be captured by the button, then it would bubble up through its parent elements, until it reaches the document element.
Capturing: In the capturing model, an event starts at the outermost element and propagates down through its children. For example, if a user clicks on a button, the click event would first be captured by the document element, then it would propagate down through its child elements, until it reaches the button.
“this” keyword
this
is the object that the function is a property of.
In JavaScript, the this
keyword refers to an object.
this
has dynamic scope
It also depends on how the function is called.
- Gives methods access to their objects
- Execute the same code for multiple objects
⚠ In an object, if we have a function that has another function in it with the keyword this, if we call the second word this, it will not return the object but will return the window object. This can be solved with the arrow function or return function2.bind(this). This happens because of the lexical reading of the Javascript Engine.
Arrow functions do not have their own this
binding. Instead, they "capture" this
from the surrounding lexical context, which is wherever the function was defined. This is known as "lexical scoping."
const obj = {
traditionalFunction: function () {
console.log(this); // `this` refers to `obj`
},
arrowFunction: () => {
console.log(this); // `this` refers to the global object, not `obj`
}
};
obj.traditionalFunction(); // Logs `obj`
obj.arrowFunction(); // Logs the global object (in a browser, `window`)
In JavaScript, the call()
, apply()
, and bind()
methods allow you to call a function with a specified this
value and arguments.
- The
call()
method allows you to call a function with a specifiedthis
value and arguments, provided as a comma-separated list. - The
apply()
method is similar to thecall()
method, but the arguments are passed as an array. - The
bind()
method returns a new function with a specifiedthis
value and arguments, partially or completely bound.
Context vs Scope
Scope pertains to the visibility of the variables, and context refers to the object within which a function is executed.
Types in JS:
Primitives (such as numbers, strings, and booleans) are stored directly in memory, whereas non-primitives (such as objects and arrays) are stored as references. Primitives are more memory efficient
Primitives are data that represent a single value in memory. Primitive data types are not mutable. To change the value of primitive data in JS we have to assign it to a new value in memory without altering the previous one.
- string
- number
- booleans
- undefined -the absence of a definition
- null — the absence of value
- Symbol.
⚠Exception typeof undefined = Object
Non-Primitive are objects. An object holds a reference/address of a single key-value pair or many key-value pairs. When we refer to an object we refer to an address in memory with key and value pair.
- objects
- arrays
- functions
Arrays and functions are also objects.
Immutability not changing the data or the state. We do this by copying the data or the state and modify the copy.
Built-in objects in JS :
- null
- undefined
- infinity / Math
- String
- Number
- Boolean
Objects are passed by reference.
A way to change object2 without changing obj1 is with spread operators. Spread operators work only for the first layer of the object. For other layers is better to use JSON or deep cloning.
obj2={...obj1}
How to compare objects?
function shallowEqual(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (object1[key] !== object2[key]) {
return false;
}}
return true;}
Type Coercion means JS transforms a type into another type. Type coercion happens when you use in JS “==” . This also happens if ( 1) = true / if (0) = false.
JavaScript is a dynamically typed language, but TypeScript is a statically typed language.
“Dynamic typing” means that the type of a value is determined at runtime, while “static typing” means that the type of a value is determined at compile time.
Function vs objects Function are callable objects.
Define function & Invoke
function abc (){} & abc() ➡abc.call()
const obj={ two :function (){return 2 }} ➡obj.two()
const four = new Function ('return 4') ➡ four()
High order function :
- function that accepts another function as a parameter
- functions that return another function
- Clone Objects
You can safely clone, then mutate, your input. Just leave the original one untouched.
- Spread syntax (
…
syntax) is the easiest way to shallowly clone objects. JSON.parse(JSON.stringify(object))
is the easiest way to deeply clone objects.
Closures
Closures are features of JS that take care that function has access to variables outside of it. A closure is a combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
Advantages of Closures are memory efficiency and encapsulation.
Encapsulation — hiding information that has not to be seen outside. A function declared in another function is not directly exposed to the global scope.
Memory efficiency — by keeping values in memory, closures can be more memory efficient than using global variables or passing values as arguments between functions, because you don’t need to continually allocate and deallocate memory for these values.
Closures are also used in JavaScript callbacks and event handlers, where they provide a way to remember and access variables from the parent scope even after the event has completed.
function createCounter() {
let count = 0;
return function() {
return count++;
}
}
const counter = createCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
const closures = function (){
let count =0
return function increment (){
count ++
return count
}
}
const incrementFn=closures()
console.log(incrementFn()) // return 1
console.log(incrementFn()) // return 2
Prototypes
In JavaScript, prototypal inheritance is a mechanism for creating new objects that inherit properties and methods from existing objects. This is done using the prototype property of an object, which points to another object from which the properties and methods are inherited.
The Object.create()
method is used to create a new object with a specific prototype, and the Object.getPrototypeOf()
method is used to get the prototype of an object.
Here are some examples of how prototypal inheritance can be used in JavaScript:
- Creating a new object that inherits from an existing object:
const parent = {
name: 'John',
age: 30,
sayHello: function() {
console.log('Hello, my name is ' + this.name);
}
}
const child = Object.create(parent);
console.log(child.name); // "John"
console.log(child.age); // 30
child.sayHello(); // "Hello, my name is John"
2. Adding new properties and methods to an object
const parent = {
name: 'John',
age: 30,
sayHello: function() {
console.log('Hello, my name is ' + this.name);
}
}
const child = Object.create(parent);
child.gender = 'female';
child.sayGender = function(){console.log('I am '+ this.gender)}
console.log(child.gender); // "female"
child.sayGender(); // "I am female"
In order to see what properties are inherited can be used
obj.hasOwnProperty(property)
class Character {
constructor(name,weapon){
this.name=name;
this.weapon=weapon}
attack(){
return 'attack with' + this.weapon;
}
}
class Elf extends Character{ // extends the prototype to point to character
constructor (name,weapon,type){ // constructor gets run when is instaciated a new elf
super(name,weapon); // extends super class to Character
this.type=house}
attack(){
return 'I dont attack with' + param
}
const dolby = new Elf('Doby','cloth', 'house') // use new to create new instances
dolby.attack('sword')// return i dont attack with sword
const samurai=new Character('Jack' , 'sword')
samurai.attack()// attack with swordj
When using extends in a class we need to use super
.
console.log(dolby instance Elf) // true
console.log(dolby instance Character) // true
Dolby is an instance of a class.
Extends inherit something of a class its properties.
Private and Public in OOP.
class Elf extends Character{ // extends the prototype to point to character
#age=54 // with hashtag we can add private state
constructor (name,weapon,type){ // constructor gets run when is instaciated a new elf
super(name,weapon); // extends super class to Character
this.type=house}
}
Pillars of OOP
- encapsulation: organize box in boxes which can interact with each other: makes code easier to maintain and reusable
- abstraction: hiding the complexity to use
- inheritance: inheriting from other classes to prevent rewriting
- polymorphism: call the same method on different object and each object to respond in a different way
Promises
A promise is an object that may produce a single value sometimes in a future. Either a resolve value or a reject value. A promise can have three status: pending, fulfilled, or rejected.
Promises serve same things as callbacks.
const promise = new Promise((resolve,rejects)=>{
if(true){
resolve('Stuff Work')}
else{
reject('Error, its broke')}
})
promise
.then(result=> result + '!')
.then(result2=>{
// throw Error
console.log(result2)}
.catch(()=>console.log(error))
Catch only runs if any error is ‘throw’.
Promise.all([promise1, promise2, promise3])
.then(values=>{
console.log(values})
// Will return and array of responses
// If any of promise fails all of them will fail, will go in a catch
fetch return a promise that is pending. Promises can succeed or fail once.
ES8 → async/await
The goal using async await is to make code more readable and synchronous. Async await are promises under the hood, is syntactic sugar for promises.
Await has to be used in front of any function that returns a promise.
In order to catch errors instead of then catch in async await need to use try catch.
async function getData() {
const promise1 = fetch('https://jsonplaceholder.typicode.com/posts/1').then(response => response.json());
const promise2 = fetch('https://jsonplaceholder.typicode.com/posts/2').then(response => response.json());
const promise3 = fetch('https://jsonplaceholder.typicode.com/posts/3').then(response => response.json());
const results = await Promise.all([promise1, promise2, promise3]);
console.log(results);
}
getData();
Spread operators in ES9 ES2018 to review
Job queue — or microtask que is another queue similar with callback queu e that has priority over
Axios vs Fetch
Axios has a simpler syntax compared to the Fetch API, especially when it comes to error handling and sending data in the request body. Axios automatically serializes request data as JSON, and it provides a more convenient way to handle errors.
Axios provides a built-in way to cancel a request, which can be useful in certain scenarios. With the Fetch API, you need to implement cancellation yourself by using AbortController.
GET request does not have body.
//Fetch
fetch('https://example.com/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer my-secret-token'
},
body: JSON.stringify({
key1: 'value1',
key2: 'value2'
})
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
// Axios
axios.post('https://example.com/api/data', {
key1: 'value1',
key2: 'value2',
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer my-secret-token'
}
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
Paralel or sqeuencial or race in async code
Race > Paralel > Sequence
The Promise.race()
static method takes an iterable of promises as input and returns a single Promise
. This returned promise settles with the eventual state of the first promise that settles.
Paralel use Promise.all. use Promise.allSettled in order not to get error if one of the promise fails.
Sequence when using await for each function hence inside of async code is syncron.
Browser creates a new thread per tab.
Error Handling
Types of error:
- new Error
- new SyntaxError
- new ReferenceError
//Error is a constructor function
new Error('oopsie')
throw new Error(); // Error gets thrown and stops the program,
// throw is used to display error
Error has three properties :
- error.name
- error.message
- error.stack — to get the stack trace
Catch is important in order the program is not stop running.
- try {} catch{}
- catch()
async function (){
try{
await(axios.post....)
}
catch(err){
console.log(err.message)
}
}
Functional Programming
Functional programming separates data in functions
- clear to understand
- easy to extend
- easy to understand
- easy to maintain
- memory efficient
Compose is the idea that any sort of data transformation should be obvious. A method to compose multiple function together. Is a system design. Pipe can change the order on how function are called.
const compose =(f,g)=>data=>f(g(data))
const multiplyBy3=(num)=>num*3
const makePositive=(num)=>Math.abs(num)
const multiplyBy3AndAbsolute=compose(mutiplyBy3,makePositive)
multiplyBy3AndAbsolute(-50) // return 150
Memorization — Catching a method to store values in order to use them later on in order to speed up applications.
Modules
Highly self contained, and group together, with they re own functionality, built to be move around and work.
OOP — Classes
class GoodGreeter {
name: string;
constructor() {
this.name = "hello";
}
}
let greeter= new GoodGreeter()
TypeScript
Interfaces and types
Types refeers to a primitives whereas interface refeers to a object.
An interface can extend one or multiple interfaces. Using the extends
keyword, a new interface can inherit all the properties and methods of an existing interface while also adding new properties.
Record<Keys, Type>
Constructs an object type whose property keys are Keys
and whose property values are Type
. This utility can be used to map the properties of a type to another type.
type CatName = "miffy" | "boris" | "mordred";
interface CatInfo {
age: number;
breed: string;
}
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
};
Pick<Type, Keys>
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
todo;
const todo: TodoPreview
Omit<Type, Keys>
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
createdAt: 1615544252770,
};
todo;
const todo: TodoPreview
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
const todoInfo: TodoInfo = {
title: "Pick up kids",
description: "Kindergarten closes at 5pm",
};
todoInfo;
const todoInfo: TodoInfo
Type Guards
using in
function move(pet: Fish | Bird) {
if ("swim" in pet) {
return pet.swim();
}
return pet.fly();
}
typeof
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
instanceof
if (padder instanceof SpaceRepeatingPadder) {
padder;
let padder: SpaceRepeatingPadder
}
!
// postfix ! lets you short circuit the nullability
user!.email!.length;
Generic types
function logItems<T>(items: T[]): void {
items.forEach(item => console.log(item));
}
logItems<number>([1, 2, 3]); // Logs: 1, 2, 3
logItems<string>(["a", "b", "c"]); // Logs: a, b, c
- scheme refeers to the protocol of comunication between browser and ip
- ip is an adress of a server
- browser does a TCP connection with the DNS
- does a handshake for https conneciton
- browser see the response and display the content
TLS is an encryption and authentication protocol designed to secure Internet communications. A TLS handshake is the process that kicks off a communication session that uses TLS. During a TLS handshake, the two communicating sides exchange messages to acknowledge each other, verify each other, establish the cryptographic algorithms they will use, and agree on session keys.
TLS ( transport layer security)
- client (hello)->server
- server(hello) -> client
- server(public key ) -> client
- client with public key from server encrypts session key- > server
- now with session key can comunicate secure
- this is tls 1.2
- tls 1.3 is more optimisez
Web Broswer
Rendering engline has a few steps
- parsing — to parse html(unconventional) and css and js (conventional)
- render tree — render tree generated while dom tre is constructed
- layout — is executed base when fonts are changed or
- paint — display things on the page
Happy Coding! 💻