Javascript Tutorial
Javascript Tutorial
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;
Undefined: A special data type. When a variable is declared but not initialized then its default value is
undefined i.e: let a;
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.
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,
RegExp: The RegExp object is also a built-in JavaScript object that represents a regular expression.
Non-primitive types are mutable. That means that certain methods can alter them.
Dynamic typing
JavaScript is a dynamically typed (loosely typed) language. JavaScript automatically determines the
variables' data type for you.
To find the type of a variable as a string value, you can use the typeof operator. For example,
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.
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.
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.
https://www.jsv9000.app
http://latentflip.com/loupe
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 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.
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).
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;
}
}
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.
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.
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: 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.
- 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:
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.
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();
}
};
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
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();
}
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.
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).
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:
first(2).then(second).then(third).then(function(response){
console.log(response);
});
Example 2
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
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:
In ECMAScript 2015, a shorthand notation is available, so that the keyword function is no longer
necessary.
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.
let i = 0
let a = {
['foo' + ++i]: i,
['foo' + ++i]: i,
['foo' + ++i]: i
}
console.log(a);
Example 2:
const obj = {}
const name = "Bablu", id = 1;
obj[name] = name;
obj[id] = id;
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.
obj[name] = obj[id];
Now the object has been changed because we have modified obj[name] identifier’s value with a different
one.
Spread properties
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
Example 1
const personObj = {
greet() {
console.log(`Hello, my name is ${this.name}!`);
}
}
function Person(name) {
this.name = name;
}
Person.prototype = personObj;
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 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 personPrototype = {
getInfo() {
return `Name: ${this.name}, Age: ${this.age}`;
}
};
#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.
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}`;
}
};
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
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;
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
console.log('Original state');
console.log(user); // { name: 'John' }
console.log(user[1]); // undefined
console.log(user.__proto__); // {}
console.log(user.length); // undefined
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
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
eat() {
return `${this.name} is eating!`;
}
sleep() {
return `${this.name} is going to sleep!`;
}
wakeUp() {
return `${this.name} is waking up!`;
}
climbTrees() {
return `${this.name} is climbing trees!`;
}
dailyRoutine() {
return `${this.climbTrees()} ${super.wakeUp()} ${super.eat()} ${super.sleep()}`;
}
}
function display(content) {
console.log(content);
}
// 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 swimmingMonsterCreator(monster) {
return {
...monster,
...attackerAndWalker(monster),
...swimmer(monster)
}
}
function flyingSwimmingMonsterCreator(monster) {
return {
...monster,
...attackerAndWalker(monster),
...flier(monster),
...swimmer(monster),
}
}
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
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
}
// 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
}
// After modification
newEmployee.name = "Faruk";
console.log(employee.name); // Bablu
console.log(newEmployee.name); // Faruk
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.
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 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;
}
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
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';
// block-scoped variable
let c = 'hello';
greet();
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
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.
1. Data properties
2. Accessor properties
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:
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;
}
};
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
//accessor property(setter)
get getName() {
return this.firstName;
}
//accessor property(setter)
set changeName(newName) {
this.firstName = newName;
}
}
console.log(student1.firstName); // Bablu
console.log(student1.firstName); // Faruk
console.log(student1.getName); // Faruk
Pure Functions
1. Is 𝗱𝗲𝘁𝗲𝗿𝗺𝗶𝗻𝗶𝘀𝘁𝗶𝗰: Given the same input, always returns the same output
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
● 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:
Or
We could rewrite the above function as follows that’d be for each argument one function sequentially
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
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:
// 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:
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
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 pipe
const outputFinal = pipe(square, double)(3);
console.log(outputFinal); // 18
N.B: We can use Lodash’s flowRight (compose) and flow (pipe) functions.
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.
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));
}
};
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);
},
// 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!");
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.
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);
}
}
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)
}
// Observer
class NewsSubscriber {
constructor(name) {
this.name = name;
}
// 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!
// 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 patterns can be categorized in multiple ways, but the most popular one are the following:
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:
These types of design patterns deal with multi-threaded programming paradigms. Some of the popular
ones are:
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
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.
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.
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.
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.”
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.
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”.
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();
function log(num){
if(num > 5){
return;
}
console.log(num); // 1, 2, 3, 4, 5
log(num + 1);
}
log(1);
function countDown(fromNumber) {
console.log(fromNumber);
if (nextNumber > 0) {
countDown(nextNumber);
}
}
countDown(3); // 3, 2, 1
function sum(n) {
if (n <= 1) {
return n;
}
return n + sum(n - 1);
}
sum(3); // 6
3 + sum(2) // 3 + 3 = 6
2 + sum(1) // 2 + 1 = 3, since sum(1) will return 1 from the base case
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
// OR
map = new Map([
['1', 1],
['2', 2],
['3', 3]
]);
map.has('1'); // true
map.has('10'); // false
map.get('1'); // 1
map.get('10'); // undefined
map.set('4', 4);
map.delete('4');
map.size; // 3
6. It deletes all elements from the map
map.clear();
map.size; // 0
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
// OR
set = new Set([1, 2, 3, 4, 4, 3]); // 1, 2, 3, 4
set.has(1);
set
.add(1)
.add(5); // set is now 1, 2, 3, 4, 5
6. The values() method returns a new Iterator object that contains the values for each element in the Set
object in insertion order.
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:
Example 02:
console.time('Array');
deleteFromArr(arr, n);
console.timeEnd('Array');
console.time('Set');
set.delete(n);
console.timeEnd('Set');
Array: 1.122ms
Set: 0.015ms
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
console.time('Array');
arr.push(n);
console.timeEnd('Array');
console.time('Set');
set.add(n);
console.timeEnd('Set');
Array: 0.018ms
Set: 0.003ms
We can also use Set as an array like this const arr = [..set];
How 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.
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;
}
But, if instead of declaring a variable you make variable assignment: var a = 4; then the assigned value 4
will prevail.
Best Practices
For example:
const users = [
{id: 1, name: 'Bablu'},
{id: 2, name: 'Rahim'},
{id: 3, name: 'Karim'},
{id: 4, name: 'John'},
];
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;
}, []);
// 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
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
// 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
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