DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on

Javascript concept every developer should know!!!

I have gathered important points that can be revisited for quick recall of JS concepts.

1. Call Stack

It’s a data structure which records the function calls, basically where in the program we are. If we call a function to execute , we push something on to the stack, and when we return from a function, we pop off the top of the stack.

function greeting() {
  // [1] Some code here
  sayHi();
  // [2] Some code here
}
function sayHi() {
  return "Hi!";
}

// Invoke the `greeting` function
greeting();

// [3] Some code here

Enter fullscreen mode Exit fullscreen mode

The code above would be executed like this:

  1. Ignore all functions, until it reaches the greeting() function invocation.
  2. Add the greeting() function to the call stack list.
  3. Execute all lines of code inside the greeting() function.
  4. Get to the sayHi() function invocation.
  5. Add the sayHi() function to the call stack list.
  6. Execute all lines of code inside the sayHi() function, until reaches its end.
  7. Return execution to the line that invoked sayHi() and continue executing the rest of the greeting() function.
  8. Delete the sayHi() function from our call stack list.
  9. When everything inside the greeting() function has been executed, return to its invoking line to continue executing the rest of the JS code.
  10. Delete the greeting() function from the call stack list.

In summary
The key takeaways from the call stack are:

  1. It is single-threaded. Meaning it can only do one thing at a time.
  2. Code execution is synchronous.
  3. A function invocation creates a stack frame that occupies a temporary memory.
  4. It works as a LIFO — Last In, First Out data structure.

2. Primitive Types

Image description

  1. Primitive values
  2. Non-primitive values or Composite(object references)

Data types that are known as primitive values in JavaScript are numbers, strings, booleans, null, undefined. Objects such as functions and arrays are referred to as non-primitive values.

The fundamental difference between primitives and non-primitives is that primitives are immutable(cannot be ) and non-primitives are mutable.

PRIMITIVES

Primitives are known as being immutable data types because there is no way to change a primitive value once it gets created.

var string = 'This is a string.';
string[1] = 'H'console.log(string)
// 'This is a string.'
Enter fullscreen mode Exit fullscreen mode

Primitives are compared by value. Two values are strictly equal if they have the same value.

var number1 = 5;
var number2 = 5;
number1 === number 2; // true
var string1 = 'This is a string.';
var string2 = 'This is a string.';
string1 === string2; // true

Enter fullscreen mode Exit fullscreen mode

Here the variable for primitives stores the value, so they are always copied or passed by value.

NON-PRIMITIVES

Non-primitive values are mutable data types. The value of an object can be changed after it gets created.

var arr = [ 'one', 'two', 'three', 'four', 'five' ];
arr[1] = 'TWO';
console.log(arr) 
// [ 'one', 'TWO', 'three', 'four', 'five' ];
Enter fullscreen mode Exit fullscreen mode

Objects are not compared by value. This means that even if two objects have the same properties and values, they are not strictly equal. Same goes for arrays. Even if they have the same elements that are in the same order, they are not strictly equal.

var obj1 = { 'cat': 'playful' };
var obj2 = { 'cat': 'playful' };
obj1 === obj2;  // false
var arr1 = [ 1, 2, 3, 4, 5 ];
var arr2 = [ 1, 2, 3, 4, 5 ];
arr1 === arr2;  // false
Enter fullscreen mode Exit fullscreen mode

Non primitive values can also be referred to as reference types because they are being compared by reference instead of value. Two objects are only strictly equal if they refer to the same underlying object.

var obj3 = { 'car' : 'purple' }
var obj4 = obj3;obj3 === obj4;  // true
Enter fullscreen mode Exit fullscreen mode

You can check if two objects are the same by doing a deep equals comparison to go through each of the properties to determine if two objects have the exact same properties.

Here the variables for non-primitives only store references of those objects. So they will always be copied or passed by reference.


3. Value Types and Reference Types

Primitive Types
The in-memory value of a primitive type is it's actual value (e.g. boolean true, number 42). A primitive type can be stored in the fixed amount of memory available.

null
undefined
Boolean
Number
String
Primitive types are also known as: scalar types or simple types.

Reference Types
A reference type can contain other values. Since the contents of a reference type can not fit in the fixed amount of memory available for a variable, the in-memory value of a reference type is the reference itself (a memory address).

Array
Object
Function
Reference types are also known as: complex types or container types.

Code Examples
Copying a primitive:

var a = 13         // assign `13` to `a`
var b = a          // copy the value of `a` to `b`
b = 37             // assign `37` to `b`
console.log(a)     // => 13
Enter fullscreen mode Exit fullscreen mode

The original was not changed, we can only change the copy.

Copying a reference:

var a = { c: 13 }  // assign the reference of a new object to `a`
var b = a          // copy the reference of the object inside `a` to new variable `b`
b.c = 37           // modify the contents of the object `b` refers to
console.log(a)     // => { c: 37 }
Enter fullscreen mode Exit fullscreen mode

The original was also changed, since the reference got copied.


4. Implicit, Explicit, Nominal, Structuring and Duck Typing

Image description

Type Coercion refers to the process of automatic or implicit conversion of values from one data type to another. This includes conversion from Number to String, String to Number, Boolean to Number etc. when different types of operators are applied to the values.

In case the behavior of the implicit conversion is not sure, the constructors of a data type can be used to convert any value to that datatype, like the Number(), String() or Boolean() constructor.

For eg:

String(123) // explicit
123 + ''    // implicit
Enter fullscreen mode Exit fullscreen mode

5. == vs === vs typeof

JavaScript provides both strict(===, !==) and type-converting(==, !=) equality comparison. The strict operators take type of variable in consideration, while non-strict operators make type correction/conversion based upon values of variables. The strict operators follow the below conditions for different types,

Two strings are strictly equal when they have the same sequence of characters, same length, and same characters in corresponding positions.
Two numbers are strictly equal when they are numerically equal. i.e, Having the same number value. There are two special cases in this,
NaN is not equal to anything, including NaN.
Positive and negative zeros are equal to one another.
Two Boolean operands are strictly equal if both are true or both are false.
Two objects are strictly equal if they refer to the same Object.
Null and Undefined types are not equal with ===, but equal with ==. i.e, null===undefined --> false but null==undefined --> true

Some of the example which covers the above cases,

0 == false   // true
0 === false  // false
1 == "1"     // true
1 === "1"    // false
null == undefined // true
null === undefined // false
'0' == false // true
'0' === false // false
[]==[] or []===[] //false, refer different objects in memory
{}=={} or {}==={} //false, refer different objects in memory
Enter fullscreen mode Exit fullscreen mode

6. Function Scope, Block Scope and Lexical Scope

Image description
Variable scope is the location where a variable is defined and the context where other pieces of your code can access and manipulate it. In other words, the variable scope defines where your variables can be obtained to work with it either to be displayed or do arithmetic operations on it. Function, block, and lexical scopes are going to be discussed throughout this article.

Function Scope
Function scope is where the variable is visible only within a function, and it cannot be accessed outside the curly brackets of the function. In javascript, var is the keyword that is used to declare a variable for a function scope accessibility.

Block Scope
Block Scope is the area within if, switch conditions and for, while loops. In ES6, const and let are the keywords that are used to define variables for a block scope accessibility, which means that those variables exist only within the corresponding block.

Lexical Scope
Lexical scope means that the childrens' scope has access to the variables declared in the parent scope, so if we have 2 nested functions, the inner function can access the variable declared in the outer function using any of the keywords used in javascript either with var, let or const. This means that the children's functions are lexically bound to their parents.


7. Expression vs Statement

Image description

JavaScript distinguishes expressions and statements. An expression is any valid unit of code that resolves to a value. A statement is a unit of code that performs an action.
But its necessary to understand:

  1. Expressions return value, statements do not.
  2. All expressions are statements, but not otherwise. You cannot use a statement in place of an expression. Some examples:

1. Statements

let x = 0;
function add(a, b) { return a + b; }
if (true) { console.log('Hi'); }
Enter fullscreen mode Exit fullscreen mode

2. Expressions

x;          // Resolves to 0
3 + x;      // Resolves to 3
add(1, 2);  // Resolves to 3
Enter fullscreen mode Exit fullscreen mode

Anywhere JavaScript expects a statement, you can also write an expression. This kind of statement is called an expression statement. Conversely, you cannot write a statement where JavaScript expects an expression.


8. IIFE, Modules and Namespaces

IIFE(Immediately Invoked Function Expression)
IIFE (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it is defined. The signature of it would be as below,

(function () {
  // logic here
})();
Enter fullscreen mode Exit fullscreen mode

The primary reason to use an IIFE is to obtain data privacy because any variables declared within the IIFE cannot be accessed by the outside world. i.e, If you try to access variables with IIFE then it throws an error as below,

(function () {
  var message = "IIFE";
  console.log(message);
})();
Enter fullscreen mode Exit fullscreen mode

console.log(message); //Error: message is not defined

Modules
Modules refer to small units of independent, reusable code and also act as the foundation of many JavaScript design patterns. Most of the JavaScript modules export an object literal, a function, or a constructor

Namespace
Even though JavaScript lacks namespaces, we can use Objects , IIFE to create namespaces.

  1. Using Object Literal Notation: Let's wrap variables and functions inside an Object literal which acts as a namespace. After that you can access them using object notation
var namespaceOne = {
   function func1() {
       console.log("This is a first definition");
   }
}
var namespaceTwo = {
     function func1() {
         console.log("This is a second definition");
     }
 }
namespaceOne.func1(); // This is a first definition
namespaceTwo.func1(); // This is a second definition
Enter fullscreen mode Exit fullscreen mode
  1. Using IIFE (Immediately invoked function expression): The outer pair of parentheses of IIFE creates a local scope for all the code inside of it and makes the anonymous function a function expression. Due to that, you can create the same function in two different function expressions to act as a namespace.
(function () {
  function fun1() {
    console.log("This is a first definition");
  }
  fun1();
})();

(function () {
  function fun1() {
    console.log("This is a second definition");
  }
  fun1();
})();
Enter fullscreen mode Exit fullscreen mode
  1. Using a block and a let/const declaration: In ECMAScript 6, you can simply use a block and a let declaration to restrict the scope of a variable to a block.
{
  let myFunction = function fun1() {
    console.log("This is a first definition");
  };
  myFunction();
}
//myFunction(): ReferenceError: myFunction is not defined.

{
  let myFunction = function fun1() {
    console.log("This is a second definition");
  };
  myFunction();
}
//myFunction(): ReferenceError: myFunction is not defined.
Enter fullscreen mode Exit fullscreen mode

9. Message Queue and Event Loop

Image description

Event loop is a running process that watches call stack & queues. If the call stack is empty, it takes first event in Task queue and pushes it to the call stack.

console.log('script start');

setTimeout(function () {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(function () {
    console.log('promise1');
  })
  .then(function () {
    console.log('promise2');
  });

console.log('script end'); 
// script start - promise1 - promise2 - setTimeout - script end
Enter fullscreen mode Exit fullscreen mode

10. setTimeout, setInterval and requestAnimationFrame

1. setTimeout
The setTimeout() method is used to call a function or evaluate an expression after a specified number of milliseconds. For example, let's log a message after 2 seconds using setTimeout method,

setTimeout(function () {
  console.log("Good morning");
}, 2000);
Enter fullscreen mode Exit fullscreen mode

2. setInterval
The setInterval() method is used to call a function or evaluate an expression at specified intervals (in milliseconds). For example, let's log a message after 2 seconds using setInterval method,

setInterval(function () {
  console.log("Good morning");
}, 2000);
Enter fullscreen mode Exit fullscreen mode

3. requestAnimationFrame
The question is most simply answered with. requestAnimationFrame produces higher quality animation completely eliminating flicker and shear that can happen when using setTimeout or setInterval, and reduce or completely remove frame skips.


11. this, call, apply and bind

Image description

  1. this
    'this' keyword

  2. Call: The call() method invokes a function with a given this value and arguments provided one by one

var employee1 = { firstName: "John", lastName: "Rodson" };
var employee2 = { firstName: "Jimmy", lastName: "Baily" };

function invite(greeting1, greeting2) {
  console.log(
    greeting1 + " " + this.firstName + " " + this.lastName + ", " + greeting2
  );
}

invite.call(employee1, "Hello", "How are you?"); // Hello John Rodson, How are you?
invite.call(employee2, "Hello", "How are you?"); // Hello Jimmy Baily, How are you?
Enter fullscreen mode Exit fullscreen mode
  1. Apply: Invokes the function with a given this value and allows you to pass in arguments as an array
var employee1 = { firstName: "John", lastName: "Rodson" };
var employee2 = { firstName: "Jimmy", lastName: "Baily" };

function invite(greeting1, greeting2) {
  console.log(
    greeting1 + " " + this.firstName + " " + this.lastName + ", " + greeting2
  );
}

invite.apply(employee1, ["Hello", "How are you?"]); // Hello John Rodson, How are you?
invite.apply(employee2, ["Hello", "How are you?"]); // Hello Jimmy Baily, How are you?
Enter fullscreen mode Exit fullscreen mode
  1. bind: returns a new function, allowing you to pass any number of arguments
var employee1 = { firstName: "John", lastName: "Rodson" };
var employee2 = { firstName: "Jimmy", lastName: "Baily" };

function invite(greeting1, greeting2) {
  console.log(
    greeting1 + " " + this.firstName + " " + this.lastName + ", " + greeting2
  );
}

var inviteEmployee1 = invite.bind(employee1);
var inviteEmployee2 = invite.bind(employee2);
inviteEmployee1("Hello", "How are you?"); // Hello John Rodson, How are you?
inviteEmployee2("Hello", "How are you?"); // Hello Jimmy Baily, How are you?
Enter fullscreen mode Exit fullscreen mode

Call and apply are pretty interchangeable. Both execute the current function immediately. You need to decide whether it’s easier to send in an array or a comma separated list of arguments. You can remember by treating Call is for comma (separated list) and Apply is for Array.

Whereas Bind creates a new function that will have this set to the first parameter passed to bind().


12. Objects creation in JavaScript

There are many ways to create objects in javascript as below

Object constructor:

The simplest way to create an empty object is using the Object constructor. Currently this approach is not recommended.

var object = new Object();
Enter fullscreen mode Exit fullscreen mode

Object's create method:

The create method of Object creates a new object by passing the prototype object as a parameter

var object = Object.create(null);
Enter fullscreen mode Exit fullscreen mode

Object literal syntax:

The object literal syntax (or object initializer), is a comma-separated set of name-value pairs wrapped in curly braces.

var object = {
     name: "Sudheer",
     age: 34
};

Enter fullscreen mode Exit fullscreen mode

Object literal property values can be of any data type, including array, function, and nested object.
Note: This is an easiest way to create an object

Function constructor:

Create any function and apply the new operator to create object instances,

function Person(name) {
  this.name = name;
  this.age = 21;
}
var object = new Person("Sudheer");
Enter fullscreen mode Exit fullscreen mode

Function constructor with prototype:

This is similar to function constructor but it uses prototype for their properties and methods,

function Person() {}
Person.prototype.name = "Sudheer";
var object = new Person();
Enter fullscreen mode Exit fullscreen mode

This is equivalent to an instance created with an object create method with a function prototype and then call that function with an instance and parameters as arguments.

function func() {};

new func(x, y, z);
(OR)

// Create a new instance using function prototype.
var newInstance = Object.create(func.prototype)

// Call the function
var result = func.call(newInstance, x, y, z),

// If the result is a non-null object then use it otherwise just use the new instance.
console.log(result && typeof result === 'object' ? result : newInstance);
Enter fullscreen mode Exit fullscreen mode

ES6 Class syntax:

ES6 introduces class feature to create the objects

class Person {
  constructor(name) {
    this.name = name;
  }
}

var object = new Person("Sudheer");
Enter fullscreen mode Exit fullscreen mode

Singleton pattern:

A Singleton is an object which can only be instantiated one time. Repeated calls to its constructor return the same instance and this way one can ensure that they don't accidentally create multiple instances.

var object = new (function () {
  this.name = "Sudheer";
})();
Enter fullscreen mode Exit fullscreen mode

12.Prototype Inheritance and Prototype Chain

Prototype chaining is used to build new types of objects based on existing ones. It is similar to inheritance in a class based language.
js_object_prototypes


13. Object.create and Object.assign

The Object.create() method is used to create a new object with the specified prototype object and properties. i.e, It uses an existing object as the prototype of the newly created object. It returns a new object with the specified prototype object and properties.

const user = {
  name: "John",
  printInfo: function () {
    console.log(`My name is ${this.name}.`);
  },
};

const admin = Object.create(user);

admin.name = "Nick"; // Remember that "name" is a property set on "admin" but not on "user" object

admin.printInfo(); // My name is Nick

Enter fullscreen mode Exit fullscreen mode

You can use the Object.assign() method which is used to copy the values and properties from one or more source objects to a target object. It returns the target object which has properties and values copied from the target object. The syntax would be as below,

Object.assign(target, ...sources);
Let's take example with one source and one target object,

const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };

const returnedTarget = Object.assign(target, source);

console.log(target); // { a: 1, b: 3, c: 4 }

console.log(returnedTarget); // { a: 1, b: 3, c: 4 }
Enter fullscreen mode Exit fullscreen mode

As observed in the above code, there is a common property(b) from source to target so it's value has been overwritten.
Below are the some of main applications of Object.assign() method,

  1. It is used for cloning an object.
  2. It is used to merge objects with the same properties.

13. Array functions(map, reduce, filter etc.)

Image description
Essential-Array-Functions

14. Closures

Image description
Intro
Closure is a function inside another function that has access to outer function variables

Example 👇

function foo() {
     let cat= '🐈'
     function bar() {
          console.log(cat, ' is a cat!')
     }
     bar()
}
foo()
Enter fullscreen mode Exit fullscreen mode

Explanation
In the above code, foo() created a local variable called cat and a function named bar(). The bar() function is an inner function that is defined inside foo().

Note that bar() don't have it's own variables. However, since inner function have access to variables of outer functions, bar() can access the variable cat.

Reason
Why do we learn it❓
Answer ➡️ These are used when one wants to extend the behavior such as pass variables, methods, etc. from an outer function to the inner function.😎

JavaScript is not a pure Object-Oriented Programming language, but you can achieve most of the OOP based behavior through closures.

Another Example:
A closure is the combination of a function and the lexical environment within which that function was declared. i.e, It is an inner function that has access to the outer or enclosing function’s variables. The closure has three scope chains

Own scope where variables defined between its curly brackets
Outer function’s variables
Global variables
Let's take an example of closure concept,

function Welcome(name) {
  var greetingInfo = function (message) {
    console.log(message + " " + name);
  };
  return greetingInfo;
}
var myFunction = Welcome("John");
myFunction("Welcome "); //Output: Welcome John
myFunction("Hello Mr."); //output: Hello Mr.John
Enter fullscreen mode Exit fullscreen mode

As per the above code, the inner function(i.e, greetingInfo) has access to the variables in the outer function scope(i.e, Welcome) even after the outer function has returned.


15. Functions

What are lambda or arrow functions
An arrow function is a shorter syntax for a function expression and does not have its own this, arguments, super, or new.target. These functions are best suited for non-method functions, and they cannot be used as constructors.

What is a first class function
In Javascript, functions are first class objects. First-class functions means when functions in that language are treated like any other variable.

For example, in such a language, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable. For example, in the below example, handler functions assigned to a listener

const handler = () => console.log("This is a click handler function");
document.addEventListener("click", handler);
Enter fullscreen mode Exit fullscreen mode

What is a first order function
First-order function is a function that doesn’t accept another function as an argument and doesn’t return a function as its return value.

const firstOrder = () => console.log("I am a first order function!");
Enter fullscreen mode Exit fullscreen mode

What is a higher order function
Higher-order function is a function that accepts another function as an argument or returns a function as a return value or both.

const firstOrderFunc = () =>
  console.log("Hello, I am a First order function");
const higherOrder = (ReturnFirstOrderFunc) => ReturnFirstOrderFunc();
higherOrder(firstOrderFunc);
Enter fullscreen mode Exit fullscreen mode

What is a unary function
Unary function (i.e. monadic) is a function that accepts exactly one argument. It stands for a single argument accepted by a function.

Let us take an example of unary function,

const unaryFunction = (a) => console.log(a + 10); // Add 10 to the given argument and display the value
Enter fullscreen mode Exit fullscreen mode

What is the currying function
Currying is the process of taking a function with multiple arguments and turning it into a sequence of functions each with only a single argument. Currying is named after a mathematician Haskell Curry. By applying currying, a n-ary function turns it into a unary function.

Let's take an example of n-ary function and how it turns into a currying function,

const multiArgFunction = (a, b, c) => a + b + c;
console.log(multiArgFunction(1, 2, 3)); // 6

const curryUnaryFunction = (a) => (b) => (c) => a + b + c;
curryUnaryFunction(1); // returns a function: b => c =>  1 + b + c
curryUnaryFunction(1)(2); // returns a function: c => 3 + c
curryUnaryFunction(1)(2)(3); // returns the number 6
Curried functions are great to improve code reusability and functional composition.
Enter fullscreen mode Exit fullscreen mode

What is a pure function
A Pure function is a function where the return value is only determined by its arguments without any side effects. i.e, If you call a function with the same arguments 'n' number of times and 'n' number of places in the application then it will always return the same value.

Let's take an example to see the difference between pure and impure functions,

//Impure
let numberArray = [];
const impureAddNumber = (number) => numberArray.push(number);
//Pure
const pureAddNumber = (number) => (argNumberArray) =>
  argNumberArray.concat([number]);

//Display the results
console.log(impureAddNumber(6)); // returns 1
console.log(numberArray); // returns [6]
console.log(pureAddNumber(7)(numberArray)); // returns [6, 7]
console.log(numberArray); // returns [6]
Enter fullscreen mode Exit fullscreen mode

As per the above code snippets, the Push function is impure itself by altering the array and returning a push number index independent of the parameter value. . Whereas Concat on the other hand takes the array and concatenates it with the other array producing a whole new array without side effects. Also, the return value is a concatenation of the previous array.

Remember that Pure functions are important as they simplify unit testing without any side effects and no need for dependency injection. They also avoid tight coupling and make it harder to break your application by not having any side effects. These principles are coming together with Immutability concept of ES6 by giving preference to const over let usage.

What is a callback function
A callback function is a function passed into another function as an argument. This function is invoked inside the outer function to complete an action. Let's take a simple example of how to use callback function

function callbackFunction(name) {
  console.log("Hello " + name);
}

function outerFunction(callback) {
  let name = prompt("Please enter your name.");
  callback(name);
}

outerFunction(callbackFunction);

Enter fullscreen mode Exit fullscreen mode

Why do we need callbacks
The callbacks are needed because javascript is an event driven language. That means instead of waiting for a response javascript will keep executing while listening for other events. Let's take an example with the first function invoking an API call(simulated by setTimeout) and the next function which logs the message.

function firstFunction() {
  // Simulate a code delay
  setTimeout(function () {
    console.log("First function called");
  }, 1000);
}
function secondFunction() {
  console.log("Second function called");
}
firstFunction();
secondFunction();

Output;
// Second function called
// First function called
Enter fullscreen mode Exit fullscreen mode

As observed from the output, javascript didn't wait for the response of the first function and the remaining code block got executed. So callbacks are used in a way to make sure that certain code doesn’t execute until the other code finishes execution.

16. var, let, const

Image description


17. Promises

Image description

A promise is an object that may produce a single value some time in the future with either a resolved value or a reason that it’s not resolved(for example, network error). It will be in one of the 3 possible states: fulfilled, rejected, or pending.

The syntax of Promise creation looks like below,

const promise = new Promise(function (resolve, reject) {
  // promise description
});
The usage of a promise would be as below,

const promise = new Promise(
  (resolve) => {
    setTimeout(() => {
      resolve("I'm a Promise!");
    }, 5000);
  },
  (reject) => {}
);

promise.then((value) => console.log(value))
Enter fullscreen mode Exit fullscreen mode

Why do you need a promise
Promises are used to handle asynchronous operations. They provide an alternative approach for callbacks by reducing the callback hell and writing the cleaner code.

What are the three states of promise
Promises have three states:

Pending: This is an initial state of the Promise before an operation begins
Fulfilled: This state indicates that the specified operation was completed.
Rejected: This state indicates that the operation did not complete. In this case an error value will be thrown.

What are the different ways to deal with Asynchronous Code
Below are the list of different ways to deal with Asynchronous code.

  1. Callbacks
  2. Promises
  3. Async/await
  4. Third-party libraries such as async.js,bluebird etc

*
*
*
*
*
*
*

🎊THIS POST IS UNDER DEVELOPMENT. MANY MORE TOPICS TO COME🎉

Top comments (0)