0% found this document useful (0 votes)
7 views

Javascript Tutorial

JavaScript is a dynamic programming language used for web development, featuring primitive and non-primitive data types, dynamic typing, and an event loop for managing asynchronous operations. It includes concepts like execution context, closures, and the differences between regular and arrow functions, which affect how 'this' is referenced. Additionally, JavaScript supports hoisting and lexical scoping, allowing inner functions to access variables from their outer functions.

Uploaded by

itaminul
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

Javascript Tutorial

JavaScript is a dynamic programming language used for web development, featuring primitive and non-primitive data types, dynamic typing, and an event loop for managing asynchronous operations. It includes concepts like execution context, closures, and the differences between regular and arrow functions, which affect how 'this' is referenced. Additionally, JavaScript supports hoisting and lexical scoping, allowing inner functions to access variables from their outer functions.

Uploaded by

itaminul
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 58

Basic Javascript

What is JavaScript?

JavaScript is a dynamic programming language that's used for web development, web applications, game
development, and lots more. It allows us to implement dynamic features on web pages that cannot be done
with only HTML and CSS.

Data types

Data types basically specify what kind of data can be stored and manipulated within a program.
There are two basic data types in JavaScript which can be divided into two main categories: primitive (or
primary), and non-primitive (or reference).

Primitive Types

Primitive data types can hold only one value at a time. The following are the primitive data types.

Boolean: Any of two values: true or false i.e: const isOk = true;

Null: Denotes a null value i.e: let a = null;

Undefined: A special data type. When a variable is declared but not initialized then its default value is
undefined i.e: let a;

Number: An integer or a floating-point number i.e: 3, 3.234, 3e-2 etc.

BigInt: An integer with arbitrary precision i.e: 900719925124740999n, 1n etc.

String: Represents textual data i.e: 'hello world!', "hello world!", and `hello world!`

Symbol: Data type whose instances are unique and immutable i.e: let value = Symbol('hello');

Primitives are immutable (they do not have such methods or properties that can alter them).
Non-primitive types

Non-primitive or reference data types can hold collections of values and more complex entities. The
following are the non-primitive data types.

Object: An object is a collection of the key-value pairs data. i.e: const student = { name: 'Bablu', id: 10 };

Array: An array is a type of object used for storing multiple values in a single variable i.e: const colors =
["Red", "Yellow", "Green", "Orange"];

Function: The function is a callable object that executes a block of code. Since functions are also objects,
it is possible to assign them variables.

const greeting = function(){


return "Hello World!";
}

console.log(typeof greeting); // function


console.log(greeting()); // Hello World!

greeting.token = 'asdf’;
console.log(greeting.token); // asdf

Dates: The Date object is also a built-in JavaScript object. It allows you to get the user's local time by
accessing the computer system clock through the browser for example,

const d = new Date();


console.log(d) // Tue Sep 07 2021 01:58:09 GMT+0600 (Bangladesh Standard Time)

RegExp: The RegExp object is also a built-in JavaScript object that represents a regular expression.

Class: Class is also a non-primitive data type.

Non-primitive types are mutable. That means that certain methods can alter them.

For more info about the data types here MDN

Dynamic typing

JavaScript is a dynamically typed (loosely typed) language. JavaScript automatically determines the
variables' data type for you.

let foo = 42; // foo is now a number


foo = 'bar'; // foo is now a string
foo = true; // foo is now a boolean
typeof keyword

To find the type of a variable as a string value, you can use the typeof operator. For example,

const name = 'Bablu';


typeof name; // returns "string"

const number = 10;


typeof number; //returns "number"

const valueChecked = true;


typeof valueChecked; //returns "boolean"

const a = null;
typeof a; // returns "object"

Note: Notice that typeof returned "object" for the null type. This is a known issue in JavaScript since its first
release.

Concurrency Model and Event Loop


The event loop is a fundamental concept that allows asynchronous operations to be managed efficiently
without blocking the entire program. It's what enables JavaScript to handle tasks like handling user input,
making network requests, and interacting with timers without freezing the entire application.

Here's how the event loop works:

In JavaScript runtime environments like browsers and Node.js, there are typically two main queues that are
used in conjunction with the event loop:

Task Queue (also known as Callback Queue or Event Queue): When an asynchronous operation is
initiated, it's moved off the main thread and handled separately in the web API section which is part of the
JS/Node runtime environment. The results of these operations are placed in this queue.
These tasks (events) usually consist of callback functions from asynchronous operations like timers,
network requests, and user interactions. When the call stack is empty, the event loop takes tasks from the
task queue and pushes them onto the call stack for execution.

Microtask Queue (also known as Job Queue or Promise Queue): This queue is used for microtasks,
which are higher-priority tasks compared to regular tasks in the task queue. Microtasks typically include
promises and certain DOM mutations. Microtasks are executed before the next task is taken from the task
queue.

Call Stack: JavaScript is single-threaded, meaning it can execute only one piece of code at a time. The call
stack is a data structure that keeps track of the currently executing function. When a function is called, it's
pushed onto the call stack, and when it's done executing, it's popped off.

Event Loop: The event loop is a continuous process that monitors the call stack, microtask queue, and
task queue. If the call stack is empty, the event loop takes the first event from the microtask queue if it’s
empty then takes from the task queue and pushes it onto the call stack for execution.

Non-Blocking: Because the event loop allows asynchronous operations to run separately from the main
thread, the program doesn't get blocked while waiting for these operations to complete. This enables the
program to remain responsive and handle multiple tasks concurrently.

Here's a simple example to illustrate the concept:

console.log("Start");

setTimeout(() => {
console.log("Timer 1");
}, 1000);

setTimeout(() => {
console.log("Timer 2");
}, 500);

console.log("End");

Output:
Start
End
Timer 2
Timer 1

Event Loop: The Event Loop is a part of the JS/Node runtime environment. The event loop has some
responsibility among the call/execution stack, callback/task queue, and microtask queue. It simply checks
the call stack if it is empty or not, if empty then it pulls a task either from the microtask queue (priority
queue) or task queue and pushes it into the call stack which eventually executes the function/callback and
after that, the callback gets popped off the stack after the execution is done.

Concurrency model: This is based on the Non-Blocking. Let’s think about the setTimeout API which runs
inside the Web API block which is a part of the javascript runtime environment, and finally, the event loop
moves forward the callback of the setTimeout API to the callback queue as soon as the setTimeout function
execution is done (when the duration is over). Later on, the event loop again moves it forward to the call
stack to be executed. We are seeing that when the program pointer goes to the setTimeout() function it
didn’t execute instead it is handed over to the Web API as this is part of it and its declaration is over there
and the call stack started executing another task, so technically Javascript is performing multiple tasks at
the same time though it’s a single-threaded scripting programming language.

To try it online here are some links below

https://www.jsv9000.app
http://latentflip.com/loupe

What is Context/Execution Context?


In JavaScript, “context” refers to an object. Within the object, the keyword “this” refers to that object (i.e.
“self”). When a method is executed the keyword “this” refers to the object that the method is executed in.

What are the Execution Context Types?


Execution context (EC) is defined as the run time environment in which the JavaScript code is executed. By
the environment, the JavaScript engine has access to code at a particular time.

Normally execution context in JavaScript is of two types:

1. Global execution context (GEC): This is the default execution context in which JS code starts its
execution when the file first loads in the browser. All of the global code i.e. code which is not inside
any function or object is executed under the global execution context. GEC cannot be more than
one because only one global environment is possible for JS code execution as the JS engine is
single threaded.

2. Functional execution context (FEC): Functional execution context is defined as the context
created by the JS engine whenever it finds any function call. Each function has its own execution
context. It can be more than one. While executing the global execution context code, if the JS
engine finds a function call, it creates a new functional execution context for that function. In the
browser context, if the code is executing in strict mode, then the value of this is undefined else it is
the window object in the function execution context.

Execution context stack (ECS) or Call stack:

Execution context stack is a stack data structure, i.e. last in first out data structure, to store all the execution
stacks created during the life cycle of the script. Global execution context is present by default in the
execution context stack and it is at the bottom of the stack. While executing the global execution context
code, if JS engines find a function call, it creates a functional execution context for that function and pushes
it on top of the execution context stack. JS engine executes the function whose execution context is at the
top of the execution context stack. Once all the code of the function is executed, JS engines pop out that
function’s execution context and then again starts executing the next one and so on until all functions are
executed.

What are implicit and explicit bindings?


If we pass a new context into a function using either call, apply, or bind method then that function will
point its owner to that particular context/object. This process is called explicit binding/indirect invocation
other than that by default it’s implicit binding/direct invocation.

The Difference Between Regular Functions and Arrow Functions


Regular functions are created through function declarations or expressions that are constructible and
callable. However, the arrow functions are only callable but not constructible.

Simply arrow functions can never be used as constructor functions. Hence, they can never be invoked with
the new keyword. It does not have its own context and always refers to the outer context but the regular
functions have a context and that context is dynamic (based on the invocation type).

The Context or `this` Keyword in Different Types of Function Invocations


Regular Function

Inside a regular JavaScript function, this value (aka the execution context) is dynamic.

The dynamic context means that the value of this depends on how the function is invoked. In JavaScript,
there are 4 ways you can invoke a regular function.

1. During a simple/direct invocation the value of `this` equals to the global object (or undefined if the
function runs in strict mode).

2. During an indirect invocation using myFunc.call(thisVal, arg1, ..., argN) or myFunc.apply(thisVal, [arg1,
..., argN]) the value of `this` equals to the first argument.

For example:

const person = {
name: 'Bablu',
dob: '1988.11.10',
}

const person2 = {
name: 'Rahim',
dob: '1989.11.10',
}

const person3 = {
dob: '1987.11.10',
calculateAge (todayYear) {
const dob = new Date(this.dob).getFullYear();
todayYear = new Date(todayYear).getFullYear();
return todayYear - dob;
}
}

person3.calculateAge.call(person, new Date()); // 35

function calculateAge (todayYear) {


const dob = new Date(this.dob).getFullYear();
todayYear = new Date(todayYear).getFullYear();
return todayYear - dob;
}

calculateAge.call(person, new Date()); // 35

3. During a method invocation the value of `this` is the object owning the method.

4. During a constructor invocation using the new keyword `this` equals to the newly created instance.
Arrow Function

The arrow function doesn't define its own execution context. In other words, the arrow function resolves the
this lexically. The `this` value inside of an arrow function always equals to the `this` value of the outer
function.

=> `this` resolved lexically is one of the great features of arrow functions. That means that it preserves the
outer function’s or method’s scope/context. As the arrow function doesn't define its own this that’s why no
more const self = this or innerFn.bind(this) workarounds for the inner function.

=> The indirect invocation of an arrow function using myArrowFunc.call(thisVal) or


myArrowFunc.apply(thisVal) doesn't change the value of `this`; the context value is always resolved
lexically.

For Example:

let person = {
name: 'Bablu',
id: 1,
showTasks: function() {
const _this = this;
return function showDetails() {
console.log('inner fn this', this); // here the this refers the global object instead of its owner
return `Name: ${_this.name}`;
}
}
};

let person = {
name: 'Bablu’',
id: 1,
showTasks: function() {
return showDetails = () => {
return `Name: ${this.name}`;
}
}
};

arguments Object

Inside the body of a regular function, the arguments keyword is a special array-like object containing a list
of arguments which the function has been invoked with. On the other side, no arguments special keyword
is defined inside an arrow function. But if it's an inner function then the arguments object of the outer
function can be accessed through it even using the rest parameter.
Implicit return

If the return statement is missing, or there's no expression after the return statement, the regular function
implicitly returns undefined. But the inline arrow function is implicitly returned without the use of the return
keyword.

Methods on Classes

The regular functions are the usual way to define methods on classes. Sometimes you'd need to supply the
method as a callback to the web APIs, for example to setTimeout(), setInterval(), or to an event listener. In
such cases, you might encounter difficulties accessing `this` value. It happens because the method is
separated/extracted from the object at that time and the method loses its context and is converted into a
new function and then its context becomes the global/window object.

Let's bind `this` value manually to the right context:

setTimeout(obj.logName.bind(obj), 1000);

Here obj.logName.bind(obj) binds `this` value to the obj instance. Now you're sure that the method
doesn't lose the context.

Alternatively, you can use the arrow functions as methods inside classes. You can use a method as a
callback without any manual binding of `this`.

Now, in contrast with regular functions, the method defined using an arrow function binds `this` lexically to
the class instance.

Blocking & Non-blocking

Javascript is known as a single-threaded programming language at the same time event-driven


programming language.

Blocking: It flows the stack data structure (last in first out) and executes events synchronously.

Non-blocking: It flows queue data structure (first in first out) and executes events asynchronously.

What is Hoisting?

Hoisting is a mechanism where all declarations are moved to the top of their containing scope before
executing any code. In JavaScript, all declarations (functions, variables, or classes) are "hoisted" to the
top of their scope. In other words, they are parsed or evaluated before any other code is executed. All
declarations in JavaScript are initially set to undefined. So it just gives us an advantage no matter where
functions, variables, and classes are declared. It has an undefined value instead of a reference error.

The expressions are opposed to declarations, which are evaluated inline.

What is lexical scope in JavaScript?


A lexical scope (sometimes it’s known as static scope) in JavaScript is that the inner (nested/closure)
functions have access to the scope of their outer functions. But the opposite is not true; the variables
defined inside a function will not be accessible from outside of that function. This helps to make private
variables because even after the outer function execution is completed and popped off the call
stack, its variables are still available for the inner function.

What is Closure in JavaScript?


A closure is a function which has access to the lexical scope. So a closure captures variables from the
lexical scope. In other words, A closure is a feature in JavaScript where an inner function has access to the
outer (enclosing) function's variables where it was initially created, even if it was being executed from
outside (even if the parent function has already been called and closed).

A closure has three scopes:

- It has access to its own scope, i.e. variables defined between its curly brackets.
- It has access to the outer function's variables.
- It has access to the global variables.

For example:

In this case, state and setState are functions that are being executed outside of their scope (useState), but
they can still access the scope (useState function’s variables) where they were initially declared inside. So
Closures give you the power to create functions with a “memory” that persists, which means you execute
the function again it will have reference to the previous execution. Let’s see another example:

const playGame = () => {


let counter = 0;
const increment = () => {
if(counter === 3){
console.log("Don't you have something better to do?")
return
}
counter+=1
console.log(counter)
}
return increment;
}

const onClick = playGame();

onClick(); // 1
onClick(); // 2
onClick(); // 3
onClick(); // Don't you have something better to do?

Exactly, closure keeps a “cache” or a “memory” of the function’s scope that the function can access even if
it is being executed outside.

Another example:
Reference link:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

What is the difference between the method's nested function and the
function's nested function?

In JavaScript, there is no inherent difference between a method's nested function and a function's nested
function. Both are functions that are defined within the scope of another function. However, there are some
contextual differences that can affect how these nested functions are accessed and behave.

Method's nested function:

When a function is defined as a method of an object, any nested functions within that method have access
to the object's properties and other methods through the this keyword.
const obj = {
value: 42,
method: function() {
const nestedFunc = function() {
console.log(this.value);
};
nestedFunc();
}
};

obj.method(); // Output: undefined

In the above example, when nestedFunc is invoked, it prints undefined because this refers to the global
object (e.g., window in a browser context) rather than the obj object. To make nestedFunc access the
correct this value, you can use const self = this or nestedFunc.bind(this) workarounds or arrow
functions which preserve the lexical this value:

const obj = {
value: 42,
method: function() {
const nestedFunc = () => {
console.log(this.value);
};
nestedFunc();
}
};

obj.method(); // Output: 42

Function's nested function:

When a function is defined without being a method of an object, any nested functions within that function do
not have an inherent this value associated with them. They operate within the lexical scope of their parent
function and can access variables and functions defined in that scope.

function outerFunction() {
const outerVar = 'Hello';

function nestedFunction() {
console.log(outerVar);
}

nestedFunction();
}

outerFunction(); // Output: Hello


In the above example, nestedFunction can access the outerVar variable defined in its parent function,
outerFunction. However, it does not have a this value associated with it, as it is not a method of an object.

The key difference between a method's nested function and a function's nested function lies in how the this
keyword behaves. Method's nested functions have access to the this value of the object they belong to,
whereas function's nested functions do not have an inherent this value associated with them and rely on
lexical scoping.

Difference between const, let, and var variables

The const and let are any block scoped variables and var is only function scoped variable like we can
access it from outside of the for loop though which was defined inside it.

For example:

{
var abc = 'abc';
let abc2 = 'abc2';
console.log(`Result: ${abc} and ${abc2}`); // Result: abc and abc2
}
console.log(`From outside of abc: ${abc}`); // From outside of abc: abc
console.log(`From outside of abc2: ${abc2}`); // Uncaught ReferenceError: abc2 is
not defined

Promises in JavaScript?

A promise is an object that may produce a value in the future we dont’ know when; either a resolved value,
or a reason (an error message) that is rejected (e.g., a network error occurred).

How can the Promise class help us?

It is now the standard way to deal with asynchronous code. Your code will actually be faster because you
can perform other tasks while waiting for an asynchronous function to finish running. On top of that, your
user interface won't freeze anymore and the users will think that your code is faster.

First of all, to use the Promise class, all you need to do is this:

// create
const promise = new Promise(function (resolve, reject) {
if (true) {
resolve("Some data"); // success
} else {
reject('An error message'); // error
}
});

// consume
promise
.then((data) => console.log(data))
.catch((err) => console.error(err));

Chaining the promises: Chaining promises is a proper way to tell JavaScript the next thing to do after an
asynchronous task is done. It's easier to explain with an example so take a look at the following code and
run it.

doSomething1()
.then(doSomething2)
.then(doSomething3)
.catch(err => console.error(err))

For example:

let first = (val) => {


return new Promise(function(resolve, reject) {
if (val) {
resolve(val);
} else {
reject(new Error('Wrong value has been passed!'))
}
});
};
let second = (val)=> {
return val+2;
};
let third = (val)=> {
return val+2;
};

first(2).then(second).then(third).then(function(response){
console.log(response);
});

Example 2

const promise = job1();

promise
.then(function(data1) {
console.log('data1', data1);
return job2();
})
.then(function(data2) {
console.log('data2', data2);
return 'Hello world';
})
.then(function(data3) {
console.log('data3', data3);
});

function job1() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('result of job 1');
}, 1000);
});
}

function job2() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('result of job 2');
}, 1000);
});
}

Ans:
data1 result of job 1
data2 result of job 2
data3 Hello world

Dynamically adding properties to objects

A JavaScript object is just a collection of key-value pairs called properties. Once you have created an
object, you might want to read or change them. Object properties can be accessed by using the dot
notation or the bracket notation.

Oftentimes, there are variables in your code that you would like to put into an object. You will see code like
this:

let a = 'foo',
b = 42,
c = {};

let o = {
a: a,
b: b,
c: c
}

With ECMAScript 2015, there is a shorter notation available to achieve the same:

// Shorthand property names (ES2015)


let o = {a, b, c}

A property of an object can also refer to a function or a getter or setter method.

In ECMAScript 2015, a shorthand notation is available, so that the keyword function is no longer
necessary.

// Shorthand method names (ES2015)


let o = {
method(parameters) {
.………
},
}
let o = {
*generator() {
...........
}
};

Computed Property

Starting with ECMAScript 2015 (ES6), the object initializer syntax also supports computed property names.
That allows you to put an expression in brackets [], that will be computed and used as the property name.
It’ll make our application so fast by removing lots of ternary or conditional operations.

// Computed property names (ES2015)


const name = "userName";
const obj = {[name]: "Bablu Ahmed", id: 12}; //{userName: "Bablu Ahmed", id: 12}

let i = 0
let a = {
['foo' + ++i]: i,
['foo' + ++i]: i,
['foo' + ++i]: i
}
console.log(a);

Output: {foo1: 1, foo2: 2, foo3: 3}

let param = 'size'


let config = {
[param]: 12,
['mobile' + param.charAt(0).toUpperCase() + param.slice(1)]: 4
}

console.log(config) // {size: 12, mobileSize: 4}

Example 2:

const obj = {}
const name = "Bablu", id = 1;
obj[name] = name;
obj[id] = id;

console.log(obj); // {1: 1, Bablu: 'Bablu'}

Now what if we do like this:

obj[name] = obj[name];

The above statement should not change output because the left side of the assignment operator is
identifier/variable and the right side of the assignment operator is value. Since the value is the same it just
replaces the original value with the same computed value.

Now what if we do like this:

obj[name] = obj[id];

console.log(obj); // {1: 1, Bablu: 1}

Now the object has been changed because we have modified obj[name] identifier’s value with a different
one.

Spread properties

Merging objects is now possible using a shorter syntax than Object.assign().

let obj1 = { foo: 'bar', x: 42 }


let obj2 = { foo: 'baz', y: 13 }

let clonedObj = { ...obj1 }


// Object { foo: "bar", x: 42 }

let mergedObj = { ...obj1, ...obj2 }


// Object { foo: "baz", x: 42, y: 13 }
Prototypes

JavaScript is a prototype based language (not class based like Java, C++, etc. even though from ES6 the
class feature has been added, behind the scenes it also uses prototypes). We know everything in
JavaScript are objects. All objects in JavaScript are instances of the Object constructor.

When an object (i.e: Array, Function, Object, Number, String, etc. ) is created that inherits properties and
methods from Object.prototype. For example, whenever we create a constructor function in JavaScript,
the JavaScript engine adds a prototype property to the function. The prototype property is basically an
object that points to the Object.prototype and where we can also attach our own methods and properties
which will be shareable across all the instances of that constructor function.

There are some ways to use prototypes in JavaScript. Let's see one by one.

#1 Using a constructor

We know in JavaScript, almost "everything" are objects. So a function is also an object and all functions
have a special property named prototype which is an object. When you call a function as a constructor and
create an object/instance, the prototype property is set as the __proto__ property for the newly
constructed object. This is something like

FunctionName.prototype === object1.__proto__ returns true as illustrated below:

Example 1

const personObj = {
greet() {
console.log(`Hello, my name is ${this.name}!`);
}
}

function Person(name) {
this.name = name;
}

Person.prototype = personObj;

const obj1 = new Person('Bablu');


obj1.greet(); // Hello, my name is Bablu!
const obj2 = new Person('Rahim’');
obj2.greet(); // Hello, my name is Rahim!

As you can see our Person function inherits properties and methods from the personObj object that are
shared across all the instances (obj1, obj2).

Example 2

function Person(name, profession) {


this.name = name;
this.profession = profession;
}

// setting a property to the prototype of Person constructor


Person.prototype.org = 'W3 Public';

const personObj = new Person('Bablu', 'Teacher');


console.log(personObj.org); // W3 Public

function Teacher(dept) {
this.dept = dept;
}

// setting a property to the prototype of Teacher constructor with the instance of the Person constructor
Teacher.prototype = personObj;

const teacherObj = new Teacher('CSE');

console.log(teacherObj); // { dept: 'CSE' }


console.log(teacherObj.name); // Bablu
console.log(teacherObj.org); // W3 public
Example 3

const personPrototype = {
getInfo() {
return `Name: ${this.name}, Age: ${this.age}`;
}
};

function Person(name, age) {


this.name = name;
this.age = age;
};

// setting ptototype value which is by default empty


Person.prototype = personPrototype;
function Teacher(name, age, subject) {
Person.call(this, name, age);
this.subject = subject;
}

const teacherObj = new Teacher(


'Bablu',
30,
'JavaScript'
);

// console.log(teacherObj.getInfo()); // TypeError: teacherObj.getInfo is not a function


console.log(teacherObj.name); // Bablu
console.log(teacherObj.subject); // JavaScript

#2 Using Object.create

Object.create() method is used to create a new object with the specified prototype object. The applications
of this method are used for implementing inheritance.

Syntax: Object.create(prototype[, propertiesObject])

prototype : It is the prototype object from which a new object has to be created.
propertiesObject : It is an optional parameter. If you want to specify properties and methods to the newly
created object during creation of the instance.

const personPrototype = {
getInfo() {
return `Name: ${this.name}, Age: ${this.age}`;
}
};

function Person(name, age) {


this.name = name;
this.age = age;
};

// setting ptototype value which is by default empty


Person.prototype = personPrototype;

function Teacher(name, age, subject) {


Person.call(this, name, age);
this.subject = subject;
}

// setting ptototype value which is by default empty


Teacher.prototype = Object.create(Person.prototype);

const teacherObj = new Teacher(


'Bablu',
30,
'JavaScript'
);

console.log(teacherObj.getInfo()); // Name: Bablu, Age: 30


console.log(teacherObj.name); // Bablu
console.log(teacherObj.subject); // JavaScript

Here the statement Teacher.prototype = Object.create(Person.prototype); we can modify it instead of


using Object.create method like so

Teacher.prototype = Person.prototype; this similar to this Teacher.prototype = personPrototype;

But this is duplicate and it will have performance issues because we’re assigning the same
personPrototype to every constructor. If we use Objecte.create method then the newly created object is
gonna point to the Person’s prototype. See the difference in the following figures

Fig. (1) Fig. (2)

In the above figure-1 we’re seeing Teacher’s prototype is empty but as this is also an object it also has a
prototype property which is actually pointing to the parent prototype in the figure 2 with getInfo() function.
We’re also seeing another prototype property right below the getInfo() function in the figure 2 which is
pointing to the master Object.

Example 2

function Person() {}
Person.prototype.personProp = 'Person val';

function Teacher() {}
const personProto = Object.create(Person.prototype);
personProto.teacherProp = 'Teacher val';
Teacher.prototype = personProto;

const teacherObj = new Teacher();


console.log(teacherObj.personProp); // Person val
console.log(teacherObj.teacherProp); // Teacher val

We know every object has a built-in prototype property. So here the personProto instance is created based
on the Person constructor prototype. So the personProto instance’s prototype will point to Person's
constructor prototype. Let’s see in the following figure.

You can also access the prototype value of an object like this Object.getPrototypeOf(personProto). If you
expand the constructor as we’re seeing in the figure you’d be able to see the details of it. Finally you’re also
seeing the master object’s prototype object, if you console Object.prototype then what you’re gonna see is
similar to this.

#3 Object.setPrototypeOf

Example 1

const user = { name: 'John' };


const arr = [1, 2, 3];

console.log('Original state');
console.log(user); // { name: 'John' }
console.log(user[1]); // undefined
console.log(user.__proto__); // {}
console.log(user.length); // undefined

Object.setPrototypeOf(user, arr); // add the arr to user’s prototype

console.log('Modified state');
console.log(user); // Array { name: 'John' }
console.log(user[1]); // 2
console.log(user.__proto__); // [ 1, 2, 3 ]
console.log(user.length); // 3
Example 2

function Person() { }
Person.prototype.personProp = 'Person val';

function Teacher() { }
const teacherProto = { teacherProp: 'Teacher val' };
Object.setPrototypeOf(teacherProto, Person.prototype); // add Person prototype to teacherProto
Teacher.prototype = teacherProto; // add teacherProto to the Teacher prototype

const teacherObj = new Teacher();


console.log(teacherObj.personProp);
console.log(teacherObj.teacherProp);

#4 Setting the __proto__ property

Example 1

function Person() {}
Person.prototype.personProp = 'Person val';
function Teacher() {}
const proto = {
teacherProp: 'Teacher val',
__proto__: Person.prototype
};
Teacher.prototype = proto;
const inst = new Teacher();
console.log(inst.personProp);
console.log(inst.teacherProp);

Example 2

const teacherObj = {
name: 'Bablu',
__proto__: {
personProp: 'Person val',
__proto__: {
teacherProp: 'Teacher val',
__proto__: Object.prototype
}
}
};
console.log(teacherObj.name);
console.log(teacherObj.teacherProp);
console.log(teacherObj.personProp);
Inheritance

Inheritance enables you to define a class that takes all the functionality from a parent class and allows you
to add more. Using class inheritance, a class can inherit all the methods and properties of another class.
Inheritance is a useful feature that allows code reusability.

Example

// parent class animal


class Animal {
constructor(name, weight) {
this.name = name;
this.weight = weight;
}

eat() {
return `${this.name} is eating!`;
}

sleep() {
return `${this.name} is going to sleep!`;
}

wakeUp() {
return `${this.name} is waking up!`;
}

// sub class gorilla

class Gorilla extends Animal {


constructor(name, weight) {
super(name, weight);
}

climbTrees() {
return `${this.name} is climbing trees!`;
}

dailyRoutine() {
return `${this.climbTrees()} ${super.wakeUp()} ${super.eat()} ${super.sleep()}`;
}
}

function display(content) {
console.log(content);
}

const gorilla = new Gorilla('George', '160Kg');


display(gorilla.sleep());
display(gorilla.dailyRoutine());

// OUTPUT:
// George is going to sleep!
// George is climbing trees! George is waking up! George is eating! George is going to sleep!

As javascript class doesn't support multiple inheritance so we can use factory functions for each
functionality and call them in a final factory function which will return an aggregation object of all objects.

function attackerAndWalker({ name }) {


return {
attack: () => console.log(`${name} attacked`),
walk: () => console.log(`${name} walked`)
}
}

function swimmer({ name }) {


return {
swim: () => console.log(`${name} swam`)
}
}

function flier({ name }) {


return {
fly: () => console.log(`${name} flew`)
}
}

function swimmingMonsterCreator(monster) {
return {
...monster,
...attackerAndWalker(monster),
...swimmer(monster)
}
}

function flyingSwimmingMonsterCreator(monster) {
return {
...monster,
...attackerAndWalker(monster),
...flier(monster),
...swimmer(monster),
}
}

const obj = flyingSwimmingMonsterCreator({ name: 'Duck' });


console.log(obj.name); // Duck
obj.attack(); // Duck attacked
obj.walk(); // Duck walked
obj.swim(); // Duck swam
obj.fly(); // Duck flew

What is aggregation in JavaScript?

An aggregate is an object which contains other objects. The ability to combine several objects into a new
one is known as aggregation. Multiple small objects are easier to manage than one large object.

For example

const obj1 = {};


const obj2 = {};
const obj3 = {};
const aggs = {...obj1, …obj2, …obj3};

Shallow and Deep Copying in JavaScript

Shallow Copy: As a reference type variable receives is an address or the location instead of a value so
when a reference variable is copied into a new reference variable using the assignment operator, a shallow
copy of the referenced object is created. In other words, when a new reference variable is assigned the
value of the old reference variable, the address stored in the old reference variable is copied into the new
one. This means both variables point to the same object in memory. As a result, if the state of the object
changes through any of the reference variables it is reflected for both. For example

const employee = {
id: "102",
name: "Bablu",
address: "Dhaka, Bangladesh",
salary: 100000
}

const newEmployee = employee; // passed by reference

// After modification
newEmployee.name = "Faruk";
console.log(employee.name) // Faruk
We can resolve this issue by using either shallow or deep copy. In a nutshell, shallow copies are used for
“flat” objects, and deep copies are used for “nested” objects.

Deep Copy: Deep copy makes a copy of all the members of the old object, allocates a separate memory
location for the new object, and then assigns the copied members to the new object. For example

const employee = {
id: "102",
name: "Bablu",
address: "Dhaka, Bangladesh",
salary: 100000
}

const newEmployee = JSON.parse(JSON.stringify(employee)); // Deep copy

// After modification
newEmployee.name = "Faruk";
console.log(employee.name); // Bablu
console.log(newEmployee.name); // Faruk

To create a deep copy, we can use the following methods:

1. const arr1 = JSON.parse(JSON.stringify(arr1)); // an object as well


2. structuredClone(), e.g., const clone = structuredClone(original);
3. Third party libraries like Lodash

To create a shallow copy, we can use the following methods:

1. const arr1 = arr1.slice(0);


2. const arr1 = [].concat(arr1); // concat with an empty array
3. const arr1 = Array.from(arr1);
4. const obj1 = Object.create({}, obj1);
5. const obj1 = Object.assign({}, obj1);
6. Using the ES6 Spread syntax […] {…}

JavaScript Scope

Scope determines the visibility and accessibility or availability of a variable in certain parts of the code. We
know that during the creation phase of the global execution context, the JavaScript engine performs the
following tasks:

1. Create the global object i.e., window in the web browser or global in Node.js.
2. Create the this object and bind it to the global object.
3. Setup a memory heap for storing variables and function references.
4. Store the variables with the initial values as undefined and function declarations/function
references in the memory heap.
After the creation phase, the global execution context moves to the execution phase. During the
execution phase, the JavaScript engine executes the code line by line, assigns the values to variables,
and executes the function calls.
For each function call, the JavaScript engine creates a new function execution context. To keep track of
all the execution contexts, including the global execution context and function execution contexts,
the JavaScript engine uses the call stack.

JavaScript has three scopes:

1. Global scope
2. Local/Function scope
3. Block scope (started from ES6)

Global Scope

When the JavaScript engine executes a script after first loading, it creates a global execution context. It
also assigns variables that you declare outside of functions to the global execution context. These
variables are in the global scope also known as global variables.

Let's see an example of a global scope variable.

let a = "hello";

function greet () {
console.log(a);
}

greet(); // hello

In the above program, variable a is declared at the top of a program and is a global variable. It means the
variable a can be used anywhere in the program.

The value of a global variable can be changed inside a function. For example,

let a = "hello";

function greet() {
a = 3;
}

// before the function call


console.log(a); // hello

// after the function call


greet();
console.log(a); // 3

Note: It is a good practice to avoid using global variables because the value of a global variable can
change in different areas in the program. It can introduce unknown results in the program.

In JavaScript, a variable can also be used without declaring it. If a variable is used without declaring it, that
variable automatically becomes a global variable.

For example,

function greet() {
a = "hello"
}

greet();

console.log(a); // hello

In the above program, variable a is a global variable.

Note: In JavaScript, there is "strict mode"; in which a variable cannot be used without declaring it.

Local Scope

We know that each function is born with a new scope. The variables that you declare inside a function or its
scope are called local variables. Variables declared with var, let and const are quite similar when declared
inside a function.

Block scope

ES6 provides the let and const keywords that allow you to declare variables in block scope.

Generally, whenever you see curly brackets {}, it is a block. It can be the area within the if, else, switch
conditions, or for, do while, and while loops. For example:

// global variable
let a = 'Hello';

function greet() {

// local variable
let b = 'World';

console.log(a + ' ' + b);


if (b == 'World') {

// block-scoped variable
let c = 'hello';

console.log(a + ' ' + b + ' ' + c);


}

// variable c cannot be accessed here


console.log(a + ' ' + b + ' ' + c); // error
}

greet();

In the above program,

● a is a global variable. It can be accessed anywhere in the program.


● b is a local variable. It can be accessed only inside the function greet.
● c is a block-scoped variable. It can be accessed only inside the if statement block.

In JavaScript, var is only function scoped, and other two the let and const keywords are block-scoped
(variable can be accessed only in the immediate block i.e: {...}).

Scope chain

Consider the following example:

var message = 'Hi';

function say() {
console.log(message);
}

say(); // Hi

In this example, we reference the variable message inside the say() function. Behind the scenes,
JavaScript performs the following:

Look up the variable message in the current context (function execution context) of the say() function. If it
cannot find any then try to find the variable message in the outer execution context which is the global
execution context. It finds the variable message. In case it doesn’t find in the global execution context then
try again searching in the prototype chain if it doesn’t find the message variable then finally throws a
reference error.
The way that JavaScript resolves a variable is by looking at it in its current scope, if it cannot find the
variable, it goes up to the outer scope, and then prototype chain which is called the scope chain.

We know that the Lexical scope is only at the function level and Lexical Scoping defines how variable
names are resolved in nested functions. The inner functions contain the scope of parent functions even if
the parent function has returned but if there is not defined that variable inside the parent function then the
inner function uses the Scope chain.

Getter & Setter (Accessors) in JavaScript

In JavaScript, there are two kinds of object properties:

1. Data properties
2. Accessor properties

Data Property

Here's an example of data property.

const student = {
// data property
firstName: 'Bablu';
};

Accessor Property

In JavaScript, accessor properties are methods that get or set the value of data of an object. For that, we
use these two keywords:

● get - to define a getter method to get the property value


● set - to define a setter method to set the property value

JavaScript Getter

In JavaScript, getter methods are used to access the properties of an object. For example,

const student = {

// data property
firstName: 'Bablu',

// accessor property(getter)
get getName() {
return this.firstName;
}
};

// accessing data property


console.log(student.firstName); // Bablu

// accessing getter methods


console.log(student.getName); // Bablu

In the above program, when accessing the value, we access the value as a property.

JavaScript Setter

In JavaScript, setter methods are used to change the values of an object. For example,

const student = {
firstName: 'Bablu',

// accessor property(setter)
get getName() {
return this.firstName;
},

// accessor property(setter)
set changeName(newName) {
this.firstName = newName;
}
};

console.log(student.firstName); // Bablu

// change the value of the firstName property using a setter


student.changeName = 'Faruk';

// accessing data property


console.log(student.firstName); // Faruk

// accessing getter methods


console.log(student.getName); // Faruk

Getters and Setters using Class

The above code is reformatted below using a Class.


class Student {
constructor(firstName) {
this.firstName = firstName;
}

//accessor property(setter)
get getName() {
return this.firstName;
}

//accessor property(setter)
set changeName(newName) {
this.firstName = newName;
}
}

const student1 = new Student('Bablu');

console.log(student1.firstName); // Bablu

// change the value of the firstName property using a setter


student1.changeName = 'Faruk';

console.log(student1.firstName); // Faruk
console.log(student1.getName); // Faruk

Pure Functions

In Functional Programming, a pure function is a function which:

1. Is 𝗱𝗲𝘁𝗲𝗿𝗺𝗶𝗻𝗶𝘀𝘁𝗶𝗰: Given the same input, always returns the same output

2. Has 𝗻𝗼 𝘀𝗶𝗱𝗲-𝗲𝗳𝗳𝗲𝗰𝘁𝘀, such as


- Reading/mutating the shared state to process the output
- Mutating your input
- I/O operations (console.log)
- HTTP calls (AJAX/fetch)
- Changing the filesystem
- Querying the DOM

By using Pure Functions code becomes:


- highly reusable
- more predictable
- much easier to test
- easier to reason about
Functions Currying and Composition

function from callable as 𝐟(𝐚, 𝐛, 𝐜) into callable as 𝐟(𝐚)(𝐛)(𝐜). Simply, the currying technique
Currying is an advanced technique of working with functions. Simply, it is a transformation that translates a

decomposes a function into a sequence of functions with a single argument.

● Currying is a checking method to make sure that you get everything you need before you proceed;
● It helps you to avoid passing the same variable again and again;
● It divides your function into multiple smaller functions that can handle one responsibility. This makes
your function pure and less prone to errors and side effects;
● It is used in functional programming to create a higher-order function.

For example:

// not curried (traditional way)


const add = (x,y) => x + y;
add(2,3); // => 5

// curried add function


const add = (x) => (y) => x + y;
add(2)(3); // 5

Or

// const sumX = add(2);


// const sumY = sumX(3);
// console.log(sumY); // 5

// curried multiplication function


const mult = x => y => y * x;
mult(2)(3); // 6

// curried string function


const buildSammy = (ingred1, ingred2, ingred3) =>
`${ingred1}, ${ingred2}, ${ingred3}`;

We could rewrite the above function as follows that’d be for each argument one function sequentially

const buildSammy = ingred1 => ingred2 => ingred3 =>


`${ingred1}, ${ingred2}, ${ingred3}`;

const mySammy = buildSammy("turkey")("cheese")("bread");


console.log('mySammy:', mySammy); // mySammy: turkey, cheese, bread

Example 02:

function sendRequest(greet) {
return function (name) {
return function (message) {
return `${greet} ${name} ${message}`;
}
}
}

Function Composition

The composition is opposite to the currying. Composing is kind of making two or more into one, and the
currying is to make one into two or more. Function composition is an approach where the result of one
function is passed onto the next function, which is passed to another until the final function is executed for
the final result. In other words, function composition is the process of combining two or more
functions to produce a new function. Function compositions can be composed of any number of
functions.
Functions Composition and Piping Approach

The modern approach to composing functions is to use composition and piping.

Function Composition: The function composition takes any number of functions and invokes them all one
after the other by following the left-to-right sequence.

Example 1:

Traditional Approach:

const double = (x) => x * 2;


const square = (x) => x * x;

// variant one
var output1 = double(2);
var output2 = square(output1);
console.log(output2); // 16

// variant two
var output_final = square(double(2));
console.log(output_final); // 16

Composing Approach:

// composed function of functions


const double = x => x * 2
const square = x => x * x
const compose = (fn1, fn2) => x => fn1(fn2(x));

The compose function normally takes a number of functions as parameters and then it returns another
function which takes a number as a parameter and finally composes all functions and returns a result.

Example 2:

// function composition of any number of functions


const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
const double = x => x * 2
const square = x => x * x

// function composition
const outputFinal = compose(square, double)(3);
console.log(outputFinal); // 36

N.B: The reduce() method starts at the first element and travels towards the last, whereas the
reduceRight() method starts at the last element and travels backwards the first.
Function Piping: This is the same as the functions composition but function invocation is done in the
reverse order.

Example:

// function composition using pipe of any number of functions


const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const double = x => x * 2
const square = x => x * x

// function pipe
const outputFinal = pipe(square, double)(3);
console.log(outputFinal); // 18

N.B: We can use Lodash’s flowRight (compose) and flow (pipe) functions.

Detail link: https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983

What is the Design Pattern in JavaScript?

Design patterns are reusable solutions to commonly occurring problems in software design. They are
proven solutions, easily reusable and expressive. They lower the size of your codebase, prevent future
refactoring, and make your code easier to understand by other developers.

PubSub Design Pattern: The Publish-Subscribe (PubSub) design pattern is a behavioral design pattern
used in software development. It is also known as the Observer pattern and is part of the broader family of
event-driven programming patterns. The PubSub pattern involves two main components: publishers and
subscribers.

1. Publishers: These are objects or components that produce events or messages. They don't know
who or what will handle those events; they simply publish them.

2. Subscribers: These are objects or components that express interest in receiving and handling
specific events or messages. Subscribers subscribe to one or more types of events and receive
notifications when those events occur.

The key idea behind the PubSub pattern is that it decouples the sender (publisher) from the receiver
(subscriber). Publishers are not aware of the subscribers, and subscribers are not aware of the publishers.
This decoupling makes it easy to add or remove subscribers without affecting the publisher and vice versa.

Here are some examples of implementing the PubSub pattern in JavaScript:

Example 01:
const pubSub = {
events: {},
subscribe(event, callback) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
},
publish(event, data) {
if (this.events[event]) this.events[event].forEach(callback => callback(data));
}
};

pubSub.subscribe('update', data => console.log(data));


pubSub.publish('update', 'Some update'); // Some update

Example 02:

// PubSub module
const PubSub = (function () {
const topics = {}; // Store a mapping of topics to subscribers

return {
subscribe: function (topic, subscriber) {
if (!topics[topic]) {
topics[topic] = [];
}
topics[topic].push(subscriber);
},

publish: function (topic, data) {


if (!topics[topic]) {
return;
}
topics[topic].forEach((subscriber) => {
subscriber(data);
});
},

unsubscribe: function (topic, subscriber) {


if (!topics[topic]) {
return;
}
const index = topics[topic].indexOf(subscriber);
if (index !== -1) {
topics[topic].splice(index, 1);
}
},
};
})();

// Example subscribers
function logMessage(data) {
console.log("Received message:", data);
}

function showAlert(data) {
alert("Alert: " + data);
}

// Subscribe to a topic
PubSub.subscribe("news", logMessage);
PubSub.subscribe("alerts", showAlert);

// Publish messages
PubSub.publish("news", "New article published!");
PubSub.publish("alerts", "Emergency alert!");

// Unsubscribe from a topic


PubSub.unsubscribe("news", logMessage);

// This won't trigger the logMessage function anymore


PubSub.publish("news", "Another news article");

// This will still trigger the showAlert function


PubSub.publish("alerts", "Another emergency alert");

In this example, the PubSub module provides methods for subscribing, publishing, and unsubscribing from
topics. Two example subscribers, logMessage and showAlert, subscribe to different topics and react when
events are published. Subscribers can be added or removed independently of the publishers,
demonstrating the decoupling characteristic of the PubSub pattern.

Observer Design Pattern: The Observer design pattern is a behavioral design pattern that defines a one-
to-many dependency between objects, where one object (the subject or observable) maintains a list of its
dependents (observers) and notifies them of state changes, typically by calling one of their methods. This
pattern is used when an object's state change needs to be communicated to other objects without making
them tightly coupled.

Here are some examples of the Observer pattern in JavaScript:

Example 01:
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}

class Observer {
update(data) {
console.log(data);
}
}

const subject = new Subject();


const observer = new Observer();

subject.addObserver(observer);
subject.notify('Everyone gets pizzas!');

The main difference between this and PubSub is that the Subject knows about its observers and can
remove them. They aren’t completely decoupled like in PubSub.

Example 02:
// Subject (Observable)
class NewsPublisher {
constructor() {
this.subscribers = []; // List of subscribers (observers)
}

// Method to subscribe to the publisher


subscribe(observer) {
this.subscribers.push(observer);
}

// Method to unsubscribe from the publisher


unsubscribe(observer) {
const index = this.subscribers.indexOf(observer);
if (index !== -1) {
this.subscribers.splice(index, 1);
}
}

// Method to notify all subscribers about a new article


publishNews(article) {
this.subscribers.forEach((observer) => {
observer.notify(article);
});
}
}

// Observer
class NewsSubscriber {
constructor(name) {
this.name = name;
}

// Method to be called when notified by the publisher


notify(article) {
console.log(`${this.name} received news: ${article}`);
}
}

// Create instances of the publisher and subscribers


const newsPublisher = new NewsPublisher();

const subscriber1 = new NewsSubscriber("Subscriber 1");


const subscriber2 = new NewsSubscriber("Subscriber 2");
const subscriber3 = new NewsSubscriber("Subscriber 3");

// Subscribers subscribe to the publisher


newsPublisher.subscribe(subscriber1);
newsPublisher.subscribe(subscriber2);
newsPublisher.subscribe(subscriber3);

// Publisher notifies subscribers about a new article


newsPublisher.publishNews("Breaking news: A new discovery!");

// Output:
// Subscriber 1 received news: Breaking news: A new discovery!
// Subscriber 2 received news: Breaking news: A new discovery!
// Subscriber 3 received news: Breaking news: A new discovery!

// Unsubscribe one of the subscribers


newsPublisher.unsubscribe(subscriber2);

// Publisher notifies subscribers about another article


newsPublisher.publishNews("Latest update: Weather forecast!");

// Output:
// Subscriber 1 received news: Latest update: Weather forecast!
// Subscriber 3 received news: Latest update: Weather forecast!

In this example, NewsPublisher is the subject (observable) that maintains a list of subscribers and notifies
them when there's new news.
The NewsSubscriber is the observer that subscribes to the publisher and reacts by printing the received
news.

The Observer pattern allows for loose coupling between the publisher and subscribers. Subscribers can be
added or removed dynamically without affecting the publisher or other subscribers. When the publisher
publishes news, all subscribed observers are notified and can react accordingly. In the example,
unsubscribing subscriber2 results in it no longer receiving updates from the publisher.

Higher Order Component Design Pattern: To pass reusable logic down as props to components
throughout your application.

Within our application, we often want to use the same logic in multiple components. This logic can include
applying a certain styling to components, requiring authorization, or adding a global state.

A Higher Order Component (HOC) is a component that receives another component. The HOC contains
certain logic that we want to apply to the component that we pass as a parameter. After applying that logic,
the HOC returns the component with the additional logic.

Design Pattern Categorization

Design patterns can be categorized in multiple ways, but the most popular one are the following:

● Behavioral design patterns


● Creational design patterns
● Concurrency design patterns
● Structural design patterns
● Architectural design patterns

Behavioral Design Patterns:

These types of patterns recognize, implement, and improve communication between disparate objects in a
system. They help ensure that disparate parts of a system have synchronized information. Popular
examples of these patterns are:

● Chain of responsibility pattern


● Command pattern
● Iterator pattern
● Mediator pattern
● Memento pattern
● Observer pattern
● State pattern
● Strategy pattern
● Visitor pattern

Creational Design Patterns:


These patterns deal with object creation mechanisms which optimize object creation compared to a basic
approach.

Some of the popular design patterns in this category are:

● Factory method pattern


● Abstract factory pattern
● Builder pattern
● Prototype pattern
● Singleton pattern

Concurrency Design Patterns:

These types of design patterns deal with multi-threaded programming paradigms. Some of the popular
ones are:

● Active object pattern


● Nuclear reaction pattern
● Scheduler pattern

Structural Design Patterns:

These patterns deal with object relationships. They ensure that if one part of a system changes, the entire
system doesn’t need to change along with it. The most popular patterns in this category are:

● Adapter Pattern
● Bridge Pattern
● Composite Pattern
● Decorator Pattern
● Facade Pattern
● Flyweight Pattern
● Proxy Pattern

Architectural Design Patterns:

Design patterns which are used for architectural purposes. Some of the most famous ones are:

MVC (Model-View-Controller)
MVP (Model-View-Presenter)
MVVM (Model-View-ViewModel)

Design Principles

Design Principles are general guidelines or a set of rules that we can follow in every step of programming.
On the other hand, Design Patterns are proven solutions that solve commonly occurring problems while
developing software systems.
Separation of Concerns Principle: Separation of concerns is a concept invented by Edsgar Dijkstra. It
simply means that one should focus on only one aspect of a subject at a time. That is it, nothing more
actually. Originally, there wasn’t even a link to software design.

In the context of being effective with software design, separation of concerns is a design guideline which
states that we should separate the problem into different sections or subproblems so that we can focus on
the implementation and on the evolution of each one of those subproblems.

DRY (Don’t repeat yourself):


KISS (Keep it simple, stupid):
YAGNI (You ain’t gonna need it):

What are the S.O.L.I.D Principles?

S: Single Responsibility Principle


O: Open-Closed Principle.
L: Liskov-Substitution Principle.
I: Interface Segregation Principle.
D: Dependency Inversion Principle.

Single Responsibility Principle: A class should only have one job/responsibility.

Open-Closed Principle: Objects or entities should be open for extension, but closed for modification.

Liskov-Substitution Principle: Any subclass object should be substitutable for the superclass object from
which it is derived.
Interface Segregation Principle: A client should never be forced to implement an interface, but the
interface should be based on the clients.

Dependency Inversion Principle: High-level modules should not import anything from low-level modules;
they should both depend on abstractions.

𝗦𝗼𝗳𝘁𝘄𝗮𝗿𝗲 𝗔𝗿𝗰𝗵𝗶𝘁𝗲𝗰𝘁𝘂𝗿𝗲: Modern SOLID

The SOLID principles are a time-tested rubric for creating quality software.

But in a world of multi-paradigm programming and cloud computing, are they still valid?

Absolutely yes!

Daniel Orner restates each of the five SOLID principles to a more general statement that can apply to OO,
FP, or multi-paradigm programming.

1. Single responsibility principle


Original definition: “There should never be more than one reason for a class to change.”

New definition: “Each module should do one thing and do it well.”

2. Open-closed principle

Original definition: “Software entities should be open for extension, but closed for modification.”

New definition: “You should be able to use and add to a module without rewriting it.”

3. Liskov substitution principle

Original definition: “If S is a subtype of T, then objects of type T may be replaced with objects of type S
without altering any of the desirable properties of the program.”

New definition: You should be able to substitute one thing for another if those things are declared to behave
the same way.

4. Interface segregation principle

Original definition: “Many client-specific interfaces are better than one general-purpose interface.”

New definition: “Don’t show your clients more than they need to see”.

5. Dependency inversion principle

Original & New definition: “Depend upon abstractions, not concretions.”

To restate “modern SOLID” one more time:

- Don’t surprise the people who read your code.


- Don’t surprise the people who use your code.
- Don’t overwhelm the people who read your code.
- Use sane boundaries for your code.
- Use the right level of coupling—keep things together that belong together, and keep them apart if they
belong apart.

Others that are yet to cover:

API Gateway
REST principles

Recursion
Recursion is a process of calling itself. A function that calls itself is called a recursive function. To prevent
infinite recursion, we can use the if...else statement where one branch makes the recursive call, and the
other doesn't. The condition that stops a recursive function from calling itself is known as the base case.
Typically, you will find the recursive functions in data structures like binary trees, graphs and algorithms
such as binary search and quicksort.

function recurse() {
if(true) {
// stop calling itself
//...
} else {
recurse();
}
}

recurse();

For example: To console log numbers from 1 to 5

function log(num){
if(num > 5){
return;
}
console.log(num); // 1, 2, 3, 4, 5
log(num + 1);
}

log(1);

Example 02: To console log numbers in reverse order

function countDown(fromNumber) {
console.log(fromNumber);

let nextNumber = fromNumber - 1;

if (nextNumber > 0) {
countDown(nextNumber);
}
}
countDown(3); // 3, 2, 1

Example 03: To sum like 1+2+3…+n

function sum(n) {
if (n <= 1) {
return n;
}
return n + sum(n - 1);
}

sum(3); // 6

This is how it’ll work as follows

3 + sum(2) // 3 + 3 = 6
2 + sum(1) // 2 + 1 = 3, since sum(1) will return 1 from the base case

Handling Key-Value Relationships with Maps

A Map is an associative data structure. It contains key-value pairs and offers fast access to values based
on keys. This is an alternative to the JS Object/Dictionary. We can create a map object with the Map
constructor and handle it as follows.

1. It creates a map

let map = new Map();

// OR
map = new Map([
['1', 1],
['2', 2],
['3', 3]
]);

2. It checks whether the map contains a key

map.has('1'); // true
map.has('10'); // false

3. It gets access to values

map.get('1'); // 1
map.get('10'); // undefined

4. It adds a new key-value pair or overwrites the value of an existing key

map.set('4', 4);

5. It deletes a key-value pair from the map

map.delete('4');
map.size; // 3
6. It deletes all elements from the map

map.clear();
map.size; // 0

Handling Unique Data With Sets

A Set is a data structure modeled after the set known from math. It is an alternative to using an array when
you only want unique elements.
The methods used by Sets to search for, delete and insert items all have a time complexity of just O(1) that
means the size of the data has virtually no bearing on the run-time of these methods!

We can create a set object with the Set constructor and handle it as follows.

1. It creates a set

let set = new Set();

// OR
set = new Set([1, 2, 3, 4, 4, 3]); // 1, 2, 3, 4

2. It checks if the Set contains a value

set.has(1);

3. It adds an unique element to the set

set
.add(1)
.add(5); // set is now 1, 2, 3, 4, 5

4. It deletes an element from the set

set.delete(1); // set is now 2, 3, 4, 5


set.length; // always 0!
set.size; // 4

5. It deletes all elements from the set

set.clear(); // set is now empty

6. The values() method returns a new Iterator object that contains the values for each element in the Set
object in insertion order.

const set1 = new Set();


set1.add(42);
set1.add('forty two');

const iterator1 = set1.values();

console.log(iterator1.next().value);
// expected output: 42

console.log(iterator1.next().value);
// expected output: "forty two"

7. The entries() method returns a new Iterator object that contains an array of [value, value] for each
element in the Set object, in insertion order.

For example:

const set1 = new Set();


set1.add(42);
set1.add('forty two');

const iterator1 = set1.entries();

for (const entry of iterator1) {


console.log(entry);
// expected output: [42, 42]
// expected output: ["forty two", "forty two"]
}

Example 02:

const mySet = new Set();


mySet.add('foobar');
mySet.add(1);
mySet.add('baz');

const setIter = mySet.entries();

console.log(setIter.next().value); // ["foobar", "foobar"]


console.log(setIter.next().value); // [1, 1]
console.log(setIter.next().value); // ["baz", "baz"]

Example 03: Deleting an item with compare to an array

let arr = [], set = new Set(), n = 1000000;


for (let i = 0; i < n; i++) {
arr.push(i);
set.add(i);
}

const deleteFromArr = (arr, item) => {


let index = arr.indexOf(item);
return index !== -1 && arr.splice(index, 1);
};

console.time('Array');
deleteFromArr(arr, n);
console.timeEnd('Array');

console.time('Set');
set.delete(n);
console.timeEnd('Set');

Array: 1.122ms
Set: 0.015ms

Example 04: Searching for an item

let result;
console.time('Array');
result = arr.indexOf(123123) !== -1;
console.timeEnd('Array');

console.time('Set');
result = set.has(123123);
console.timeEnd('Set');

Array: 0.173ms
Set: 0.023ms

Example 05: Adding an item

console.time('Array');
arr.push(n);
console.timeEnd('Array');

console.time('Set');
set.add(n);
console.timeEnd('Set');

Array: 0.018ms
Set: 0.003ms

The Set was 6.73 times faster

We can also use Set as an array like this const arr = [..set];
How to make an object immutable?

In JavaScript, we can use 𝐎𝐛𝐣𝐞𝐜𝐭.𝐟𝐫𝐞𝐞𝐳𝐞() and 𝐎𝐛𝐣𝐞𝐜𝐭.𝐬𝐞𝐚𝐥() methods to make an object


immutable.

With 𝐎𝐛𝐣𝐞𝐜𝐭.𝐟𝐫𝐞𝐞𝐳𝐞():
1-We cannot add a new property in an object. ❌
2-We cannot delete existing properties. ❌
3-We cannot update the value of existing properties. ❌

With 𝐎𝐛𝐣𝐞𝐜𝐭.𝐬𝐞𝐚𝐥():
1-We cannot add a new property in an object. ❌
2-We cannot delete existing properties. ❌
3-We can update the value of existing properties. ✅

Immutable objects can be handy in making our programs more reliable and less buggy.

What happens when JavaScript Variable Name and Function Name are
the Same?
In JavaScript both function declaration and variable declarations are hoisted to the top of the function, if
defined in a function, or the top of the global context, if outside a function. And function declaration takes
precedence over variable declarations (but not over variable assignment).
Function Declaration Overrides Variable Declaration When Hoisted.

First you declare a variable:

var a; // value of a is undefined

Second, the value of a is a function because function declaration takes precedence over variable
declarations (but not over variable assignment):

function a(x) {
return x * 2;
}

And that is what you get when you call alert(a);.

But, if instead of declaring a variable you make variable assignment: var a = 4; then the assigned value 4
will prevail.

Best Practices

Matching related good practices:


1. We can use object literal and square notation like we can replace array.find(), array.reduce(), and
switch statement with it.
2. We can use Set() constructor and array.filter() method.
3. We can first convert array to object then we can use in operator and array.filter() method.

For example:

const ids = [1, 2, 3];

const idsObj = {};


ids.forEach((value) => {
obj[value] = true;
});

const users = [
{id: 1, name: 'Bablu'},
{id: 2, name: 'Rahim'},
{id: 3, name: 'Karim'},
{id: 4, name: 'John'},
];

const foundUsers = users.filter((user) => user.id in idsObj);


const idsSet = new Set(ids);
const foundUsers = users.filter((user) => idsSet.has(user.id));

const ids = [1, 2]

const payments = [
{ id: 1, recipient: 'Alice', amount: 100 },
{ id: 2, recipient: 'Bob', amount: 150 },
{ id: 3, recipient: 'Charlie', amount: 75 },
{ id: 4, recipient: 'Bablu', amount: 100 }
];

// solution 1
// this is kind of complex code and the complexity is O(n^2) or quadratic complexity due to 2 nested loops
// if we add one more nested loop then the complexity would be O(n^3) or cubic complexity
const foundPayments = ids.reduce((acc, id) => {
const match = payments.find((payment) => payment.id === id);
if (match) {
return [...acc, match];
}
return acc;
}, []);

console.log("Found payments", foundPayments);

// solution 2
// we can use object literal or map/dictionary instead of array.find() method, therefore first convert payments
into an object and then use square notation or use built-in Map() constructor.
// Now it’d be altogether O(n) complexity or Linear time complexity

// const paymentsMap = payments.reduce((acc, payment) => {


// acc[payment.id] = payment;
// return acc;
// }, {});

// const paymentsMap = new Map(payments.map(item => [item.id, item]));

const paymentsMap = new Map();


payments.forEach((payment) => paymentsMap.set(payment.id, payment));

const filteredPayments = ids.reduce((acc, id) => {


// const match = payments.find((payment) => payment.id === id);
// const match = paymentsMap[id]; // {}
const match = paymentsMap.get(id);
if (match) {
// return acc.push(match);
return [...acc, match];
}
return acc;
}, []);

console.log('filteredPayments', filteredPayments);

// solution 3
// we can use Set object and array.filter() method, therefore first convert the array into the Set object and
use filter() method

const idsSet = new Set(ids);


const filteredPayments = payments.filter((payment) => idsSet.has(payment.id));
console.log("Found payments", filteredPayments);

// solution 4
// we can use object literal and array.filter() method, therefore first convert the array into an object and use
filter() method and the in operator

const idsObj = ids.reduce((acc, id) => {


acc[id] = true;
return acc;
}, {});
const filteredPayments = payments.filter((payment) => payment.id in idsObj);
console.log("Found payments", filteredPayments);

Some Good Code Practices:


One to Many Dictionary:

const fruitsByUserId = {};

const addFruitsToUser = (fruit, userId) => {


const id = userId || Math.round(Math.random());
const existingFruits = fruitsByUserId[id] || [];
existingFruits.push({id, fruit});
fruitsByUserId[id] = existingFruits;
}

addFruitsToUser('Apple');
addFruitsToUser('Orange', 1);
Data types
Dynamic typing
***Concurrency Model and Event Loop
**What is implicit and explicit binding?
***The Difference Between Regular Functions and Arrow Functions
***Blocking & Non-blocking
***What is hoisting?
What is context?
What is execution context?
***What is lexical scope in JavaScript?
***What is closure in JavaScript?

Operators:
Loops:
Dynamically adding properties to objects:
Variable scope:
Constructors:
**Prototypes:
**Composition
** Inheritance
What is aggregation in javascript?
***`this` and `new` keywords:
***Promises in JavaScript:
Shallow and deep copy in JavaScript
Getters & Setters in JavaScript
Array functions
Static methods
JavaScript Higher Order Functions
ES6, ES7, ES8, ES9, ES10

You might also like