Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
1 views160 pages

JavaScript Notes & Reference by Shawaiz Shahid

This document is a comprehensive guide to beginner JavaScript concepts, covering topics such as variables, data types, functions, control flow, and the Document Object Model (DOM). It includes detailed explanations of JavaScript syntax, operators, and methods, along with practical examples and best practices. The notes serve as a reference for understanding both fundamental and advanced JavaScript programming techniques.

Uploaded by

Bipul Mondal
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views160 pages

JavaScript Notes & Reference by Shawaiz Shahid

This document is a comprehensive guide to beginner JavaScript concepts, covering topics such as variables, data types, functions, control flow, and the Document Object Model (DOM). It includes detailed explanations of JavaScript syntax, operators, and methods, along with practical examples and best practices. The notes serve as a reference for understanding both fundamental and advanced JavaScript programming techniques.

Uploaded by

Bipul Mondal
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 160

JavaScript

Beginner JavaScript Notes & Reference


Version 1.0

Copyright © 2025
Email: [email protected]
LinkedIn: https://www.linkedin.com/in/shawaiz-shahid-2695181b5/
Table of Contents
Variables ................................................................................................................. 5
Global Variables ..................................................................................................... 7
Re-declaration ....................................................................................................... 7
Hoisting ................................................................................................................... 7
Variables............................................................................................................... 8
Functions .............................................................................................................. 9
Data Types ............................................................................................................. 10
Primitive Data Types ............................................................................................. 10
Non-Primitive Data Types ...................................................................................... 13
Strings .................................................................................................................. 14
Arrays ................................................................................................................... 18
Array Methods ..................................................................................................... 20
Operators .............................................................................................................. 33
Arithmetic Operators ............................................................................................. 33
Assignment Operators ........................................................................................... 34
Comparison Operators........................................................................................... 34
Logical Operators ................................................................................................. 35
Unary Operators................................................................................................... 36
Binary Operator ................................................................................................... 37
Ternary Operator.................................................................................................. 37
Bitwise Operators (Advanced) ................................................................................ 38
Other Important Operators .................................................................................... 38
Control Flow ........................................................................................................... 39
Conditional Statements ......................................................................................... 39
Loop Statements .................................................................................................. 41
Loop Control Statements ....................................................................................... 45
Functions ............................................................................................................... 46
Declaring/Defining ................................................................................................ 47
Calling/Invoking ................................................................................................... 48
Parameters and Arguments .................................................................................... 48
Return Statement ................................................................................................. 51
Function Scope .................................................................................................... 51
Default Parameters ............................................................................................... 52
Document Object Model API ...................................................................................... 52
Selecting HTML Elements ....................................................................................... 53
Manipulating HTML Elements .................................................................................. 57
Callbacks and Promises ............................................................................................ 68
Async/Await ........................................................................................................... 78
Error Handling ........................................................................................................ 80
Objects and Classes................................................................................................. 83
Fetch API ............................................................................................................... 94
Import & Export Statements ..................................................................................... 94
Lexical Scope ......................................................................................................... 96
Closures ............................................................................................................. 98
Multiple Closures ................................................................................................ 105
Closure Scope Chain ........................................................................................... 106
Module Scope ....................................................................................................... 107
this keyword ........................................................................................................ 108
Global Context / Default Binding ........................................................................... 108
Object Method / Implicit Binding ........................................................................... 109
Constructor Call / new Binding.............................................................................. 111
Explicit Binding / call, apply, bind .......................................................................... 111
Arrow Functions & this ........................................................................................ 112
Order of Precedence ........................................................................................... 113
Call, Apply, & Bind ................................................................................................. 113
Currying .............................................................................................................. 119
Partial Application ................................................................................................. 123
Function Composition ............................................................................................ 124
Pure & Impure Functions ........................................................................................ 126
Shallow Copy & Deep Copy ..................................................................................... 129
Web API .............................................................................................................. 131
Event Loop ........................................................................................................... 132
Debouncing & Throttling ......................................................................................... 137
Web Storage API ................................................................................................... 141
Local Storage (Permanent) .................................................................................. 141
Session Storage (Temporary) .............................................................................. 142
Cookies (ID Card) ............................................................................................. 143
Appendix ............................................................................................................. 144
REPL ................................................................................................................ 144
IEFI ................................................................................................................. 146
Spread Syntax (...) ............................................................................................. 146
Random Number Generator.................................................................................. 149
Random Color Generator ..................................................................................... 150
HEX Colors ..................................................................................................... 150
RGB Colors ..................................................................................................... 151
HSL Colors...................................................................................................... 152
Time Out & Time Interval .................................................................................... 152
Type Coercion .................................................................................................... 155
Converting to String ......................................................................................... 155
Converting to Number ...................................................................................... 156
Converting to Boolean ...................................................................................... 157
Truthy & Falsy Values ......................................................................................... 158
References ........................................................................................................ 160
Variables
In JavaScript, there are three main keywords you use to declare variables: var,
let, and const.

1. Var Keyword:

Scope: var is function-scoped. This means if you declare a var inside a


function, it's only accessible within that function. If you declare it outside
a function, it's globally accessible.

2. Let Keyword:

Scope: let is block-scoped. This is a crucial difference from var. A block is


anything inside {} curly braces, such as if statements, for loops, or just
standalone blocks. A let variable declared inside a block is only accessible
within that block.

3. Const Keyword:

Scope: Like let, const is block-scoped.

When to use which?

• const (Default): Use const by default. If you know a variable's value


won't change after its initial assignment (and for most variables, it
shouldn't), then const is the way to go. It makes your code more
predictable and prevents accidental reassignments.

• let (When Value Needs to Change): Use let only when you explicitly know
that the variable's value will need to be reassigned later in your code
(e.g., in a loop counter, a variable that accumulates a value, or a
variable whose state changes).

• var (Avoid): Generally, try to avoid var in modern JavaScript code unless
you're working with older codebases that already use it.
Global Variables

Re-declaration
1. You can re-declare only the same var variable multiple times without an
error.

2. You cannot re-declare the same let variable in the same scope. This helps
prevent accidental overwrites. You can reassign its value, though.

3. You cannot re-declare the same const variable within a block.

------------------------------------------------------------------------

Hoisting
Hoisting in JavaScript is a behavior where declarations of variables,
functions, and classes are conceptually "moved" to the top of their containing
scope during the compilation phase, before the code is executed. This means
that you can use a variable or call a function before it is declared in the
actual code.

Variables
Declarations are hoisted, initializations are not: Only the declaration part of
a variable is hoisted, not its assignment or initialization.

In this example, myVar's declaration is hoisted, so it exists, but its value


is undefined until the assignment line is reached.

let and const and the Temporal Dead Zone (TDZ): While let and const
declarations are also hoisted, they are not initialized with undefined like
var. Instead, they enter a "Temporal Dead Zone" (TDZ) from the beginning of
their scope until their declaration is encountered. Attempting to access them
within the TDZ will result in a ReferenceError.
Functions
Function declarations vs. Function expressions:
• Function declarations: are fully hoisted, meaning both the declaration
and the definition are moved to the top. This allows you to call a
function declaration before its appearance in the code.

• Function expressions: (where a function is assigned to a variable) are


treated like variable declarations and only the variable declaration is
hoisted, not the function definition.

Example:
When you have a var declaration and a function declaration with the same
name, the function declaration takes precedence.

The engine first hoists the function magic() { ... } declaration and
definition. Then, it hoists the var magic; declaration, which is ignored
because a function with that name already exists. The line var magic =
1; is then executed, which overwrites the function with the value 1. The
final line magic() then tries to call the value 1 as if it were a
function, which throws a TypeError.

Another Example:

• function fetch() { ... } is hoisted entirely.


• let A; is hoisted, but unlike var, let declarations are placed in a
"Temporal Dead Zone" (TDZ) and are not initialized to undefined.

------------------------------------------------------------------------

Data Types
typeof Operator: You can use the typeof operator to find out the data type of a
variable or value.

Primitive Data Types


Primitive data types represent single values. When you assign a primitive value
to a variable, you're essentially storing that direct value. When you copy a
primitive variable, the value itself is copied.

There are seven primitive data types in JavaScript:

1. String
Enclosed in single quotes ('...'), double quotes ("..."), or backticks (`...`).
Backticks (template literals) offer more features like embedding expressions.
2. Number
Represents both integer and floating-point (decimal) numbers.

3. Boolean

It can only be one of two values: true or false. It Represents a logical


entity.

4. Undefined

It represents a variable that has been declared but has not yet been assigned a
value. It's also the default return value of functions that don't explicitly
return anything.

5. Null

It's explicitly assigned by a programmer to indicate "no value" or "empty" -


Intentional Absence.

In boolean contexts, null is considered a falsy value, meaning it evaluates


to false.
Important Note: A historical quirk in JavaScript means typeof null returns
"object", which is technically incorrect but can't be changed now without
breaking a lot of existing code.

6. Symbol

Often used to create unique object property keys to avoid naming collisions,
especially in larger applications or when working with third-party code.

Every Symbol() call creates a new, unique symbol. Once a Symbol is created, its
value cannot be changed (Immutability).

7. BigInt

It represents whole numbers larger than 2^53 - 1 (the maximum safe integer that
number can reliably represent).

Created by appending n to the end of an integer.


Non-Primitive Data Types
The Non-Primitive Data Type (Object Data Type) and its sub-types can hold
collections of data and more complex entities. When you assign an object to a
variable, you're actually storing a reference to that object in memory. When
you copy an object variable, you copy the reference, not the object itself.

1. Mutability: Non-primitive data types are mutable, meaning their contents


can be modified after creation.
2. Stored by reference: Variables holding non-primitive values store
references to the objects in memory.
3. Dynamic size: Their size can change during runtime as elements or
properties are added or removed.

It includes:

• Plain Objects: Key-value pairs (like dictionaries or hash maps).

• Arrays: Ordered lists of values.

• Functions: Callable blocks of code (functions are first-class objects in


JS).

Key Points

• Primitives vs. Objects: Primitive types store values directly in memory


(typically the stack), non-primitive types store references to objects in
memory (typically the heap).

• Dynamic Typing: JavaScript is a dynamically typed language. This means


you don't have to explicitly declare the data type of a variable when you
declare it (like int x; in Java). The type is determined at runtime based
on the value it holds, and it can change.
------------------------------------------------------------------------

Strings
1. Creating Strings

You can create strings in JavaScript using three types of quotes:

• Single quotes: 'Hello'

• Double quotes: "World"

• Backticks (Template Literals): `JavaScript` (Introduced in ES6/ES2015,


offer more features.)

Why three types? Mainly to make it easy to include quotes within a string
without escaping them:

2. String Length

You can find the number of characters in a string using the .length property.

3. Accessing Characters (Indexing)

Strings are zero-indexed, meaning the first character is at index 0, the second
at 1, and so on.

You can access individual characters using bracket notation []:


To "change" a string, you have to create a new string based on the old one.

4. String Concatenation (Joining Strings)

You can join strings together using the + operator or template literals.

a. Using + operator:

b. Using Template Literals ${}:

This is generally the preferred method for complex string building as it's
cleaner and more readable. You embed expressions (like variables) directly into
the string using ${}.

Template literals also support multi-line strings without special characters:


5. Common String Methods

Strings in JavaScript are objects, which means they come with many built-in
methods (functions) that allow you to perform various operations on them.

a. Changing Case

• .toUpperCase(): Converts the string to uppercase.

• .toLowerCase(): Converts the string to lowercase.

b. Searching and Checking

c. Extracting Substrings

• .slice(startIndex, endIndex): Extracts a part of a string. endIndex is


exclusive. Can take negative indices (counts from end).
• .substring(startIndex, endIndex): Similar to slice, but doesn't accept
negative indices. Swaps arguments if startIndex is greater than endIndex.
• .substr(startIndex, length): Extracts length characters starting from
startIndex. (Older, generally prefer slice or substring).
d. Replacing Content

• .replace(searchValue, replaceValue): Replaces the first occurrence of a


substring with another.

• .replaceAll(searchValue, replaceValue): Replaces all occurrences.

e. Trimming Whitespace

• .trim(): Removes whitespace from both ends of a string.

• .trimStart() / .trimEnd(): Removes whitespace from the beginning/end


respectively.

f. Splitting Strings

• .split(separator): Splits a string into an array of substrings based on a


separator.
6. Immutability of Strings

This is a crucial concept: Strings in JavaScript are immutable.

This means once a string is created, its content cannot be changed. Any string
method that seems to "modify" a string (like toUpperCase(), replace(), slice())
actually returns a new string with the modifications, leaving the original
string untouched.

------------------------------------------------------------------------

Arrays
Arrays let you store and organize collections of values, whether they are
numbers, strings, objects, or even other arrays.

1. Creating Arrays

You can create arrays in a couple of ways:

a. Array Literal

This is the simplest and most common way to create an array. You enclose a
comma-separated list of values in square brackets [].
b. Array() Constructor (Less Common)

You can also use the Array() constructor, but it's generally less preferred due
to some nuances (especially with a single number argument).

Because of the potential for confusion with new Array(singleNumber) creating


empty slots rather than an array with that single number, the array literal []
is almost always preferred.

2. Array Length

You can find the number of elements in an array using the .length property.

3. Accessing Array Elements (Indexing)

Like strings, arrays are zero-indexed. This means the first element is at index
0, the second at 1, and so on.

You access elements using bracket notation [] with the index number.

4. Modifying Array Elements

Unlike strings, arrays are mutable. You can change the value of an element at a
specific index.
Array Methods
1. Mutating Methods (modify the original array):

• push(): Adds one or more elements to the end of an array and returns the
new length.

• pop(): Removes the last element from an array and returns that element.

• shift(): Removes the first element from an array and returns that
element.

• unshift(): Adds one or more elements to the beginning of an array and


returns the new length.

• splice(): Changes the content of an array by removing or replacing


existing elements and/or adding new elements in place.

• sort(): Sorts the elements of an array in place and returns the array.

• reverse(): Reverses the order of the elements in an array in place.

• fill(): Fills all the elements of an array from a start index to an end
index with a static value.

2. Non-Mutating Methods (return a new array or value without altering the


original):

• concat(): Merges two or more arrays and returns a new array.

• slice(): Returns a shallow copy of a portion of an array into a new array


object.

• map(): Creates a new array populated with the results of calling a


provided function on every element in the calling array.

• filter(): Creates a new array with all elements that pass the test
implemented by the provided function.

• reduce(): Executes a reducer function on each element of the array,


resulting in a single output value.

• forEach(): Executes a provided function once for each array element.


• find(): Returns the value of the first element in the array that
satisfies the provided testing function.

• findIndex(): Returns the index of the first element in the array that
satisfies the provided testing function.

• indexOf(): Returns the first index at which a given element can be found
in the array, or -1 if it is not present.

• lastIndexOf(): Returns the last index at which a given element can be


found in the array, or -1 if it is not present.

• includes(): Determines whether an array includes a certain value among


its entries, returning true or false as appropriate.

• some(): Tests whether at least one element in the array passes the test
implemented by the provided function.

• every(): Tests whether all elements in the array pass the test
implemented by the provided function.

• join(): Creates and returns a new string by concatenating all of the


elements in an array, separated by commas or a specified separator
string.

• flat(): Creates a new array with all sub-array elements recursively


"flattened" up to a specified depth.

• toString(): Returns a string representing the specified array and its


elements.

5. Adding and Removing Elements

Arrays come with powerful methods to easily add or remove elements.

a. push() and pop() (End of Array)

• push(element1, element2, ...): Adds one or more elements to the end of


the array. Returns the new length of the array.

• pop(): Removes the last element from the array. Returns the removed
element.

b. unshift() and shift() (Beginning of Array)

• unshift(element1, element2, ...): Adds one or more elements to the


beginning of the array. Returns the new length.
• shift(): Removes the first element from the array. Returns the removed
element.

6. Iterating Over Arrays (Loops)

You'll frequently need to process each element in an array.

a. for loop

b. for...of loop

This is often preferred for simple iteration over array values because it's
cleaner and directly gives you the elements.

7. Array Methods

• indexOf(element): Returns the first index at which a given element can be


found, or -1 if it is not present.

• includes(element): Returns true or false indicating whether an array


includes a certain element.
• join(separator): Joins all elements of an array into a string.

• slice(startIndex, endIndex): Returns a shallow copy of a portion of an


array into a new array object. endIndex is exclusive. Does NOT modify the
original array. startIndex is optional, if omitted, slice() starts from
index 0. endIndex is optional, if omitted, slice() extracts to the end of
the array.

• splice(startIndex, deleteCount, item1, ...): Changes the contents of an


array by removing or replacing existing elements and/or adding new
elements. Modifies the original array.

• concat(array1, array2, ...): Used to merge two or more arrays. Returns a


new array.

• reverse(): Reverses the order of the elements in an array in place.

• sort(): Sorts the elements of an array in place. By default, it sorts


alphabetically for strings and treats numbers as strings (which can be
unexpected). For numbers, you need to provide a comparison function.
8. Destructuring Arrays

A convenient way to extract values from arrays and assign them to variables.

The right side [b, a] creates a new array [10, 5]. Then, destructuring
assignment on the left side [a, b] unpacks the values from this temporary array
back into a and b in the swapped order. While a temporary array is implicitly
created, you don't explicitly declare a "third variable" as a separate
identifier.

9. Higher-Order Functions (HOF)

HOF refers to functions that take other functions as arguments or return


functions as their result.

forEach, map, filter, and reduce: these are often referred to as "higher-order
functions" because they can take other functions as arguments. These methods
are non-mutating, meaning they do not change the original array. Instead, they
return a new array (or a single value in the case of reduce) with the results.

a. forEach() method

A higher-order function that executes a provided callback function once for


each array element.

b. Array.map()

The map() method creates a new array populated with the results of calling a
provided function on every element in the calling array.

• Purpose: To transform each element in an array into a new value, creating


a new array of the same length.

• Syntax: array.map((currentValue, index, array) => {//function logic},


thisArg)

o callback: A function that is called for every element.

▪ currentValue: The current element being processed.

▪ index (optional): The index of the current element.

▪ array (optional): The array on which map was called upon.

o thisArg (optional): Value to use as this when executing callback.


c. Array.filter()

The filter() method creates a new array with all elements that pass the test
implemented by the provided function.

• Purpose: To select a subset of elements from an array based on a


condition, it creates a new array that contains only the matching
elements.

• Syntax: array.filter((currentValue, index, array) => {//function logic},


thisArg)

o callback: A function that is called for every element. It should


return true to keep the element, false otherwise.

▪ currentValue: The current element being processed.

▪ index (optional): The index of the current element.

▪ array (optional): The array filter was called upon.

o thisArg (optional): Value to use as this when executing callback.


d. Array.reduce()

The reduce() method executes a reducer function (that you provide) on each
element of the array, resulting in a single output value.

• Purpose: To "reduce" an array of values down to a single value (e.g., a


sum, a count, an object, a flattened array).

• Syntax: array.reduce(callback(accumulator, currentValue, index, array),


initialValue)

o callback: A function that is called for every element.

▪ accumulator: The value resulting from the previous callback


invocation. On the first call, it's initialValue (if provided)
or the first element (array[0]).

▪ currentValue: The current element being processed.


▪ index (optional): The index of the current element.

▪ array (optional): The array reduce was called upon.

o initialValue (optional): A value to use as the first argument to the


first call of the callback, this value will be used as the initial
value for the accumulator in the first iteration. If not provided,
array[0] is used as the initial accumulator, and iteration starts
from array[1].
Chaining Methods

One of the great benefits of these methods returning new arrays is that you can
chain them together!
10. Array.from() method

It is used when you have an array-like object (like a NodeList or arguments)


and you want to use standard array methods (map, filter, forEach, etc.) on it.

Array.from() is a flexible and efficient way to ensure you're working with true
arrays, which unlocks all the powerful array methods we've discussed (map,
filter, reduce, forEach, etc.).

The Array.from() static method creates a new, shallow-copied Array instance


from an iterable or array-like object.

• Purpose:

o To convert array-like objects (like NodeList from


document.querySelectorAll(), the arguments object in functions, or
strings) into real arrays.

o To create a new array from an iterable (like Set, Map, or custom


iterators).

o To create a new array and populate it using a mapping function


(similar to map(), but on creation).

• Syntax: Array.from(arrayLike, mapFn, thisArg)

o arrayLike: An iterable or array-like object to convert to an array.

o mapFn (optional): A map function to call on each element of the


array. It's executed before the element is added to the new array.

o thisArg (optional): Value to use as this when executing mapFn.

1. Converting a String to an Array of Characters

A string is an "iterable" (you can loop over its characters), so Array.from()


can convert it into an array where each character is an element.
2. Converting an Array-like Object (e.g., NodeList from the DOM)

When you select multiple elements from the HTML DOM using methods like
document.querySelectorAll(), they return a NodeList, which is array-like but
not a true array. This means it has a length property and you can access
elements by index, but it doesn't have array methods like map(), filter(),
forEach(), etc. Array.from() is perfect for this.

(You won't be able to run this directly in the console without a webpage, but
imagine this HTML: <div class="item">A</div><div class="item">B</div>)

3. Converting the arguments object (in older functions)

Inside a regular function (not arrow function), arguments is an array-like


object containing the arguments passed to that function.

Note: In modern JavaScript, the rest parameter syntax (...args) is generally


preferred over arguments for handling an indefinite number of arguments, as it
directly gives you a real array.
4. Using Array.from() with a Mapping Function

This is a powerful feature where you can create and map elements in a single
step. It's essentially Array.from(arrayLike).map(mapFn).

The arrayLike object can have a length property to enable Array.from() to work
on it even if it's not truly iterable. This is what we saw with { length: 5 }.

5. Converting a Set to an Array

Set is an iterable object (stores unique values). Array.from() can convert it


directly.

------------------------------------------------------------------------
Operators
Operators are special symbols or keywords that tell the JavaScript engine to
perform some operation, such as arithmetic calculations, assignments,
comparisons, or logical evaluations, on one or more values (called operands).

Arithmetic Operators
These are used to perform mathematical calculations.

• + (Addition): Adds two numbers.

• - (Subtraction): Subtracts the right operand from the left.

• * (Multiplication): Multiplies two numbers.

• / (Division): Divides the left operand by the right.

• % (Modulus/Remainder): Returns the remainder of a division.

• ** (Exponentiation - ES2016): Raises the left operand to the power of the


right operand.

• ++ (Increment): Increases the value of an operand by 1. Can be prefix


(++x) or postfix (x++).

• -- (Decrement): Decreases the value of an operand by 1. Can be prefix (--


x) or postfix (x--).

Note on + with strings: When + is used with strings, it performs string


concatenation. If one operand is a string and the other is a number, the number
will be converted to a string.
Assignment Operators
These are used to assign values to variables. The simple assignment operator is
=, but there are also "compound" assignment operators that perform an operation
and then assign the result.

• = (Assignment): Assigns the value of the right operand to the left


operand.

• += (Addition assignment): x += y is equivalent to x = x + y.

• -= (Subtraction assignment): x -= y is equivalent to x = x - y.

• *= (Multiplication assignment): x *= y is equivalent to x = x * y.

• /= (Division assignment): x /= y is equivalent to x = x / y.

• %= (Modulus assignment): x %= y is equivalent to x = x % y.

• **= (Exponentiation assignment): x **= y is equivalent to x = x ** y.

Comparison Operators
These are used to compare two values and return a boolean (true or false)
result.

• == (Loose Equality): Compares values after type coercion (tries to


convert operands to a common type before comparing). This can lead to
unexpected results.

• === (Strict Equality): Compares values without type coercion. Checks if


both value AND type are the same. Always prefer === over ==!
• != (Loose Inequality): Returns true if operands are not equal after type
coercion.

• !== (Strict Inequality): Returns true if operands are not equal in value
OR not equal in type. Always prefer !== over !=!

• > (Greater than):

• < (Less than):

• >= (Greater than or equal to):

• <= (Less than or equal to):

Logical Operators
These are used to combine or modify boolean expressions.

• && (Logical AND): Returns true if both operands are true.

• || (Logical OR): Returns true if at least one operand is true.

• ! (Logical NOT): Inverts the boolean value of the operand (turns true to
false, false to true).

• ?? (Nullish Coalescing): Returns the right-hand side operand only when


the left-hand side is null or undefined.
Unary Operators
Operators that work on a single operand. We've already seen ++ and --.

• + (Unary Plus): Tries to convert its operand to a number.

• - (Unary Minus): Tries to convert its operand to a number and then


negates it.

• typeof: Returns a string indicating the type of the operand.

• delete: Deletes a property from an object.

• void: Evaluates an expression and returns undefined.


Binary Operator
The comma operator (,) in JavaScript is a binary operator that evaluates each
of its operands (expressions) from left to right and returns the value of the
last operand.

• Evaluates multiple expressions:

It allows you to include multiple expressions within a single statement,


separated by commas.

• Returns the last value:

While all expressions are evaluated, only the result of the rightmost (last)
expression is returned as the value of the entire comma-separated expression.

• Common use case:

It is most commonly found in for loops to perform multiple initializations or


updates within the loop's header, such as for (var i = 0, j = 9; i <= j; i++,
j--).

• Not a separator for arguments:

It is distinct from the comma used to separate arguments in function calls or


elements in array literals.

Example:

Ternary Operator
Also known as Conditional Operator. This is the only operator that takes three
operands. It's a shorthand for a simple if-else statement.

• condition ? expressionIfTrue : expressionIfFalse


Bitwise Operators (Advanced)
These operators perform operations on the binary representations of numbers.
You usually won't need them unless you're doing very low-level operations or
specific optimizations.

• & (AND)

• | (OR)

• ^ (XOR)

• ~ (NOT)

• << (Left Shift)

• >> (Right Shift)

• >>> (Unsigned Right Shift)

Other Important Operators


• . (Member Access): Used to access properties of an object (e.g.,
object.property).

• [] (Bracket Notation/Property Access): Used to access properties of an


object when the property name is a variable or contains special
characters (e.g., object['property'], array[index]).

• () (Function Call): Executes a function.

• new (Constructor Call): Creates a new instance of an object using a


constructor function.

• instanceof: Checks if an object is an instance of a particular


constructor.
------------------------------------------------------------------------

Control Flow
Control Flow is where your JavaScript programs start to get "smart" and
dynamic. Up until now, your code would execute line by line, from top to
bottom. Control flow statements allow you to:

1. Make decisions: Execute different blocks of code based on certain


conditions.

2. Repeat actions: Run the same block of code multiple times.

These are fundamental to almost any non-trivial program.

Conditional Statements (Making Decisions)


These allow your code to choose which path to take based on whether a condition
is true or false.

a. if, else if, else

This is the most common way to handle conditions.

• if (condition): Executes a block of code if the condition is true.

• else if (anotherCondition): (Optional) If the first if's condition is


false, it checks this anotherCondition. You can have multiple else if
blocks.

• else: (Optional) If all preceding if and else if conditions are false,


this block of code is executed.
b. switch Statement
The switch statement is an alternative to long if-else if chains when
you're checking a single variable against multiple possible values.
• It evaluates an expression once.
• The value of the expression is then compared with the values of
each case.
• If a match is found, the code block associated with that case is
executed.
• break: It's crucial to use break at the end of each case block to
exit the switch statement. If you omit break, execution will "fall
through" to the next case (which is rarely what you want).
• default: (Optional) The default block is executed if no case
matches the expression's value.
Loop Statements (Repeating Actions)
Loops allow you to execute a block of code repeatedly until a certain condition
is met.

a. for Loop

The for loop is ideal when you know exactly how many times you want to iterate
or when you need to iterate over a sequence (like numbers or array indices).

• Initialization: Executed once at the very beginning. Often used to


declare and initialize a counter variable (e.g., let i = 0).
• Condition: Evaluated before each iteration. If true, the loop continues;
if false, the loop terminates.

• Increment/Decrement: Executed after each iteration. Often used to update


the counter (e.g., i++).

Bonus:

Remember var is a function-scoped variable, whereas let is a block-scoped


variable. To understand setTimeOut(), hop to Time Out & Time Interval.
b. while Loop

The while loop executes a block of code as long as a specified condition is


true. It's suitable when you don't know in advance how many times the loop will
run, but you have a condition that will eventually become false.

Random Number Generator in appendix for detailed information on Math.random().

c. do...while Loop
The do...while loop is similar to while, but it guarantees that the code block
will be executed at least once, because the condition is checked after the
first iteration.

d. for...of Loop

Used to iterate over iterable objects like Arrays, Strings, Maps, Sets, etc. It
directly gives you the value of each element.

e. for...in Loop

Used to iterate over the enumerable properties of an object. It gives you the
keys (property names) of the object. While it can be used for arrays, for or
for...of are generally preferred for arrays due to potential issues with
inherited properties and iteration order.
Loop Control Statements
These statements allow you to alter the normal flow of a loop.

a. break

Immediately terminates the current loop and transfers control to the statement
following the loop.

b. continue

Skips the current iteration of the loop and proceeds to the next iteration.
------------------------------------------------------------------------

Functions
Functions are truly one of the most fundamental and powerful concepts in
JavaScript (and programming in general).

Think of a function as a reusable block of code designed to perform a specific


task. They allow you to:

1. Organize your code: Break down complex problems into smaller, manageable
pieces.

2. Avoid repetition (DRY - Don't Repeat Yourself): Write a piece of code


once and use it many times.

3. Improve readability: Give meaningful names to blocks of code, making it


easier to understand their purpose.

4. Promote modularity: Create self-contained units of functionality that can


be easily tested and reused.

A function typically consists of:

• A function keyword (or arrow syntax).

• A name (optional for anonymous functions).

• A list of parameters (inputs) enclosed in parentheses ().

• A function body (the code to be executed) enclosed in curly braces {}.

• A return statement (optional) to send a value back from the function.


Declaring/Defining
There are several ways to define functions in JavaScript.

a. Function Declaration (Named Function)

This is the most common and traditional way to define a function.

• Hoisted: Function declarations are "hoisted" to the top of their scope.


This means you can call them before they are declared in your code.

b. Function Expression (Anonymous or Named)

You can define a function and assign it to a variable. This makes the function
an "expression" rather than a standalone declaration.

• Not hoisted: Function expressions are not hoisted in the same way as
declarations. You must define them before you call them.

• The function name in a named function expression (calculateMultiply


above) is usually only accessible inside the function itself.

c. Arrow Functions

Arrow functions provide a more concise syntax for writing function expressions.
They have some behavioural differences, especially concerning this keyword,
which you'll learn about later.
• Concise syntax: Especially useful for short, single-line functions.

• No this binding: They don't have their own this context, inheriting this
from the surrounding lexical context. (This is an advanced topic, don't
worry about it too much right now).

• Not hoisted: Similar to function expressions, they must be defined before


use.

Calling/Invoking
To execute the code inside a function, you "call" or "invoke" it by writing its
name followed by parentheses ().

Parameters and Arguments


• Parameters: These are the named variables listed in the function
definition's parentheses. They act as placeholders for the values the
function expects to receive.

• Arguments: These are the actual values you pass to the function when you
call it.
• If you call a function with fewer arguments than parameters, the missing
parameters will be undefined.

• If you call a function with more arguments than parameters, the extra
arguments are simply ignored (though accessible via the arguments object,
an older feature).

Methods to Access Parameters


1. The arguments Object (Older Method)

Every function has a special, array-like object called arguments that contains
all the parameters passed to the function. This is an older method and is
generally not recommended in modern code, especially with arrow functions.

• How it works: You can access parameters by their index, like


arguments[0], arguments[1], etc.

• Drawbacks:

o It's not a true array, so you can't use methods like map or filter
on it directly without converting it (Array.from(arguments)).

o It's less performant and harder to optimize.

o It doesn't work with arrow functions.

2. The Rest Parameter (...)


This is the modern and highly recommended way to handle a variable number of
parameters. The rest parameter collects all the remaining arguments into a real
array.

• How it works: You put ... before the last parameter name in the function
signature. This parameter will be an array containing all the arguments
not assigned to other parameters.

• Benefits:

o It returns a true array, so you can use all array methods on it.

o It's clear and explicit in the function signature.

o It works with arrow functions.

Without strict mode

In non-strict mode, the arguments object is linked to the function parameters.

• If you change a or b, arguments[0] and arguments[1] automatically reflect


those changes.

With "use strict"

When you enable "use strict", this link is broken:

• Changing a or b does not update arguments[0] and arguments[1].

• arguments retains the original values that were passed into the function.
• Strict mode: arguments is a static snapshot of the initial arguments.

• Non-strict mode: arguments stays in sync with parameters.

Return Statement
• The return statement is used to send a value back from the function to
the place where it was called.

• When JavaScript encounters a return statement, the function immediately


stops executing, and the specified value is returned.

• If a function doesn't have an explicit return statement, or if it has


return; without a value, it implicitly returns undefined.

Function Scope
Variables declared inside a function (with let or const) are local to that
function. They cannot be accessed from outside the function. This is a crucial
aspect of keeping your code organized and preventing naming conflicts.
Default Parameters
You can specify default values for parameters, which will be used if no
argument is provided for that parameter when the function is called.

------------------------------------------------------------------------

Document Object Model API


The DOM is a programming interface for web documents. It represents the page so
that programs (like JavaScript) can change the document structure, style, and
content. The DOM represents the document as nodes and objects; with that,
programming languages can connect to the page.

Think of it this way:


• HTML is like the blueprint or raw content of your house.

• CSS is like the interior design and paint.

• JavaScript + DOM is like the robot (JavaScript) that can pick up


furniture, repaint walls, add new rooms, or listen for someone knocking
on the door (events) – all by interacting with the structural
representation of the house (the DOM).

When a web page is loaded, the browser creates a DOM of the page.

Key Concepts of the DOM

1. Nodes: Everything in an HTML document is a node:

o The entire document is the Document node.

o Every HTML element (<html>, <body>, <p>, <div>) is an Element node.

o The text inside elements is a Text node.

o HTML attributes are Attribute nodes.

o Comments are Comment nodes. You'll primarily work with Element and
Text nodes.

2. Tree Structure: The DOM is structured like a tree. The <html> element is
the root, <body> and <head> are its children, and so on. This
hierarchical structure allows you to navigate and manipulate elements.

Selecting HTML Elements (Finding Nodes)

Before you can manipulate an element, you need to select it. JavaScript
provides several methods for this, all accessed through the document object.

Note: Refer to REPL.


a. document.body.childNodes

• Store a node in a variables

• Use variables as a node

• Use .children to ignore whitespaces (text)


• Use .firstElementChild to ignore whitespaces (text)

b. document.getElementById(id)

• Selects a single element by its unique id attribute.

• Returns the Element object or null if no element with that ID is found.

c. document.getElementsByClassName(className)

• Selects all elements with a specific class name.

• Returns an HTMLCollection (a live, array-like object) of elements.


d. document.getElementsByTagName(tagName)

• Selects all elements with a specific HTML tag name (e.g., p, div, a).

• Returns an HTMLCollection.

e. document.querySelector(cssSelector)

• Selects the first element that matches a specified CSS selector.

• Returns the Element object or null.

• Very versatile, as you can use any valid CSS selector (#id, .class, tag,
tag.class, [attribute], etc.).
f. document.querySelectorAll(cssSelector)

• Selects all elements that match a specified CSS selector.

• Returns a NodeList (a static, array-like object). Unlike HTMLCollection,


NodeList doesn't automatically update if elements are added/removed.

• You can use forEach() directly on a NodeList or convert it to a real


array using Array.from().

Recommendation: For modern JavaScript, querySelector and querySelectorAll are


generally preferred due to their flexibility with CSS selectors and the return
of NodeList which is easier to work with (e.g., forEach).

Manipulating HTML Elements (Changing Nodes)

Once you've selected an element, you can change its properties.

a. Changing Content

• .textContent: Gets or sets the text content of an element and all its
descendants. It's safe and doesn't interpret HTML.

• .innerHTML: Gets or sets the HTML content (including tags) within an


element. Be cautious with user-provided innerHTML to prevent XSS attacks.
• Edit anything directly on any webpage using:

• Custom data can be stored in HTML elements using data-* attributes. These
attributes are accessible in JavaScript through the .dataset property of
the DOM element.
b. Changing Attributes

Use .getAttribute(), .setAttribute(), .hasAttribute(), .removeAttribute().

c. Changing Styles

Use the .style property to access inline styles.

d. Managing Classes

Use the .classList property to add, remove, or toggle CSS classes. This is the
preferred way to change styles dynamically based on state, as it keeps styling
concerns in CSS. The classes are added anyway whether they exist or not.

• .add(className)

• .remove(className)

• .toggle(className)

• .contains(className)
3. Creating New Elements
You can create new HTML elements from scratch and append them to the document.

• document.createElement(tagName): Creates a new element node.

• container.appendChild(childElement): Appends a child element to the end


of a parent.

• container.prepend(childElement): Prepends a child element to the


beginning of a parent.

• container.insertBefore(newElement, referenceElement): Inserts newElement


before referenceElement.

• element.remove(): Removes the element itself from the DOM.

• container.removeChild(childElement): Removes a specified child element


within a parent node.
4. Event Handling (Responding to User Actions)

This is where the interactivity comes in! The DOM allows you to listen for and
respond to events (like clicks, key presses, mouse movements, form
submissions).

• element.addEventListener(eventName, functionToExecute): Attaches an event


listener.

o eventName: The type of event (e.g., 'click', 'mouseover', 'submit',


'keydown').

o functionToExecute: The function that will be called when the event


occurs.
Event Bubbling

It describes the process by which an event, such as a click or a keypress,


propagates through the hierarchy of HTML elements.

When an event occurs on an element, the browser doesn't just process the event
on that element alone. Instead, the event "bubbles up" from the target element
(the one that was actually clicked) to its parent, then to the parent's parent,
and so on, all the way up to the document object and the window object.

If you click the <button> with the ID child, the following happens during the
bubbling phase:

1. The event is first triggered on the <button> (#child).

2. The event "bubbles up" to the <div> with the ID parent.

3. The event "bubbles up" to the <div> with the ID grandparent.

4. The event continues bubbling up through the <body>, <html>, document, and
window.
If an event listener is attached to any of these elements, it will be triggered
as the event passes through it.

The Event Object and event.target

When an event handler is executed, it receives an event object (often named e


or event). This object contains useful information about the event. Two crucial
properties are:

• event.target: The element where the event originated (the innermost


element that was clicked). This remains the same throughout the bubbling
process.

• event.currentTarget (or this in some contexts): The element that the


event listener is currently attached to.

Stopping Event Bubbling

Sometimes, you only want the innermost element to handle the event, and you
don't want it to propagate up to the ancestors. You can stop bubbling using the
stopPropagation() method of the event object.
Why is Event Bubbling Useful? (Event Delegation)

Event bubbling is not just a quirk of the DOM; it's the foundation of a crucial
technique called Event Delegation.

Event delegation allows you to attach a single event listener to a parent


element to handle events that occur on any of its descendant elements.
When you click any <li> (including the dynamically added one), the event
bubbles up to the <ul>, and the single listener on the <ul> handles it.

------------------------------------------------------------------------

Callbacks and Promises


1. Callbacks

A Callback is simply a function that is passed as an argument to another


function, and is executed later (called "back") when a certain task is
complete.

Callbacks are the traditional way to handle asynchronous operations in


JavaScript.

Example 1: Synchronous Callback (for illustration)

You've actually already used synchronous callbacks with array methods like
forEach, map, and filter.

Example 2: Asynchronous Callback (The real use case)

setTimeout is a great example of an asynchronous callback.


The Problem with Callbacks: "Callback Hell"

While callbacks work, they can become very difficult to manage when you have
multiple asynchronous operations that depend on the results of previous ones.
This leads to deeply nested code, often called "Callback Hell" or "Pyramid of
Doom."
This structure is hard to read, debug, and maintain. This is why Promises were
introduced.

2. Promises

A Promise is an object representing the eventual completion (or failure) of an


asynchronous operation and its resulting value. It provides a cleaner, more
organized way to handle asynchronous code.

A Promise can be in one of three states:

1. Pending: The initial state; the operation is still running.

2. Fulfilled (Resolved): The operation completed successfully.

3. Rejected: The operation failed.

Creating a Promise (The new Promise() constructor)

You rarely create Promises yourself unless you are "promisifying" an older
callback-based API. However, understanding the structure is helpful:

• When resolve() and reject() are both awaited.

• When resolve() is called.

• When reject() is called.


Consuming (Using) Promises

1. Instance Methods: Methods available on a specific Promise instance. These


are used to consume or handle the result of a single Promise.

• .then(onFulfilled(result), onRejected(error)): Handles a successful


fulfillment or an optional rejection, it successfully catches rejection.

• .catch(onRejected(error)): Specifically handles errors (rejections).

• .finally(): Executes code regardless of whether the Promise was fulfilled


or rejected.

• .then() will only execute after resolve() (static method) is called.

• .catch() will only execute after reject() (static method) is called and
.then() is not handling rejection.
2. Static Methods: Methods available directly on the Promise class itself
(Promise.all(), Promise.race(), Promise.resolve(), Promise.reject(), etc.).
These are used to work with multiple Promises simultaneously.

They are designed to manage arrays of promises and return a new promise that
settles based on the outcome of the input promises.

a. Promise.all(iterable)

Promise.all() is used when you have multiple promises that you want to execute
in parallel, and you need all of them to succeed.

• Behavior: It takes an iterable (usually an array) of promises. It returns


a single promise that:

o Resolves when all input promises have resolved. The resolved value
is an array containing the results of the input promises, in the
same order as the input array.

o Rejects immediately when any of the input promises rejects. The


rejection reason is the reason of the first promise that rejected
("fail-fast" behavior).
Note: Refer to Time Out & Time Interval for understanding setTimeout().

b. Promise.race(iterable)

Promise.race() is used when you have multiple promises and you only care about
the first one to settle (either resolve or reject).

• Behavior: It returns a single promise that:

o Settles (resolves or rejects) as soon as the first input promise


settles. The returned promise's outcome and value/reason match that
of the first settled promise.

c. Promise.allSettled(iterable)

Promise.allSettled() is similar to Promise.all(), but it waits for all promises


to settle, regardless of whether they resolve or reject.

• Behavior: It returns a single promise that:

o Resolves when all input promises have settled. The resolved value is
an array of objects, each describing the outcome (status and value
or reason) of the corresponding input promise.
d. Promise.any(iterable)

Promise.any() is similar to Promise.race(), but it waits for the first


successful fulfillment.

• Behavior: It returns a single promise that:

o Resolves as soon as any of the input promises is fulfilled. The


resolved value is the value of that fulfilled promise.

o Rejects only if all input promises reject. The rejection reason is


an AggregateError containing all the rejection reasons.
Promise Nesting

Nesting promises is discouraged and leads to several problems, often referred


to as "callback hell":

1. Readability: As more asynchronous steps are added, the code becomes


deeply indented and difficult to follow.

2. Error Handling: Managing errors (.catch()) becomes complicated. A nested


.catch() only handles errors for the nested promise, while an error in
the outer promise might require a separate .catch() for the whole chain.

3. Return Values: It is harder to pass results between different levels of


the asynchronous flow.

• Can also be written as:


• Output for both approaches:

Promise Chaining

Promise chaining is the preferred approach for sequential asynchronous


operations. It involves returning a promise from a .then() callback, allowing
subsequent .then() calls to be attached to the returned promise, creating a
flat structure.

The key to chaining is to return the nested promise (p2) from the first .then()
block. This allows you to attach the next .then() outside of the first one.
------------------------------------------------------------------------

Async/Await
async and await

async and await are syntactic sugar built on top of Promises. They make
asynchronous code look and behave more like synchronous code, making it much
easier to read, write, and debug compared to raw .then() chains.

• async keyword:

o You put async before a function declaration or expression to make it


an "asynchronous function."

o An async function always returns a Promise.

▪ If the function returns a non-Promise value, it's


automatically wrapped in a resolved Promise.

▪ If the function throws an error, it's automatically wrapped in


a rejected Promise.

• await keyword:

o You can only use await inside an async function.

o await literally "waits" for a Promise to settle (resolve or reject).

o If the Promise resolves, await returns its resolved value.

o If the Promise rejects, await throws an error, which you can catch
using a try...catch block.
Now, with async/await:

------------------------------------------------------------------------
Error Handling
The try...catch...finally Block

This statement allows you to test a block of code for errors, handle those
errors, and then execute code regardless of whether an error occurred.

1. try Block

The try block contains the code that you want to monitor for potential errors.
If an error occurs within this block, execution immediately jumps to the catch
block.

2. catch Block

The catch block is executed if an error occurs anywhere within the try block.
It receives an error object (you can name it anything, but error or err are
common conventions) which contains information about the error that occurred.
Common Error Object Properties:

• name: The type of error (e.g., ReferenceError, TypeError, RangeError,


SyntaxError, Error).

• message: A human-readable description of the error.

• stack: (Non-standard but widely supported) A stack trace that shows where
the error occurred in the code. Incredibly useful for debugging.

3. finally Block

The finally block is optional, but if present, its code will always execute,
regardless of whether an error occurred in the try block or was caught by the
catch block.

Use Cases for finally:

• Cleanup: Closing files, releasing network connections, stopping timers,


or cleaning up resources that were allocated in the try block.

• Guaranteed Execution: Any code that absolutely must run after the try or
catch block finishes.
Error Handling with async/await

try...catch works seamlessly with async/await. Since await essentially


"unwraps" a resolved Promise or "throws" a rejected Promise, you can use
standard try...catch to handle asynchronous errors.
------------------------------------------------------------------------

Objects and Classes


1. Objects

In JavaScript, an object is a standalone entity, with properties and type. It's


a collection of key-value pairs where keys are strings (or Symbols) and values
can be anything: primitive data types (strings, numbers, booleans), functions,
or even other objects.

1.1. Creating Objects

There are several ways to create objects:

a. Object Literal Syntax (Most Common)

This is the simplest and most common way to create a single object.
b. new Object() Constructor (Less Common)

This is a more verbose way to create an empty object, which you then populate.

c. Using Object.create()

This method creates a new object, using an existing object as the prototype of
the newly created object. Useful for inheritance scenarios without classes.

1.2. Accessing Object Properties and Methods

You can access properties and methods using two main ways:

a. Dot Notation (Preferred for known property names)


b. Bracket Notation (Required for dynamic property names or names with
spaces/special characters)

1.3. Adding, Modifying, and Deleting Properties

Objects are mutable, meaning you can change them after creation.
1.4. this Keyword in Objects

The this keyword inside an object method refers to the object itself that the
method belongs to.

Important Note: Arrow functions handle this differently; they do not have their
own this context but inherit it from their surrounding (lexical) scope. This is
why traditional function expressions are often preferred for object methods if
you need this to refer to the object itself.

1.5. Destructuring Objects

• Basic Destructuring: You use curly braces {} to specify the properties


you want to extract, matching the variable names to the property names in
the object.
• Renaming Properties: You can assign extracted properties to variables
with different names using a colon :.

• Default Values: You can provide default values for properties in case
they are not present in the object, preventing undefined assignments.

• Nested Destructuring: You can destructure properties from nested objects


by chaining curly braces.

• Rest Property: The rest property (...) can be used to collect all
remaining properties that were not explicitly destructured into a new
object.

• Destructuring in Function Parameters: Object destructuring is commonly


used in function parameters to directly extract specific properties from
an object passed as an argument.

1.6. Object Keys & Values

A JavaScript object is made up of (key, value) pairs including user defined and
other built in pairs from prototype of Object.

Example: Conditionally check if a particular key exists.


• User defined keys and values:

• Built in keys and values: Object Methods

• Object.assign(): Copies properties from one or more source objects


to a target object.
• Object.keys(): Returns an array of a given object's own enumerable
property names.
• Object.values(): Returns an array of a given object's own
enumerable property values.
• Object.entries(): Returns an array of a given object's own
enumerable string-keyed property [key, value] pairs.
• Object.freeze(): Freezes an object, preventing any changes to its
properties.
• Object.seal(): Seals an object, preventing new properties from
being added but allowing modification of existing ones.
• Object.hasOwn(): (Static method) Returns true if the specified
object has the indicated property as its own property.
• Object.prototype.hasOwnProperty(): (Instance method)
Returns true if the object has the specified property as its own
property.
Example:

• To check the availability of user defined keys:


1.7. Object Property Shorthand

In the line const item = { name, quantity: 5 }, the name part is a shorthand.
It's a shortcut for writing name: name.

• name: This is a variable that must already exist in your code.

• { name }: This tells JavaScript to create an object property named name


and set its value to the value of the name variable.

So, if you have a variable name with a value of 'Apple', the code:

is a concise way of writing the more verbose, older syntax:

This feature helps keep your code cleaner and easier to read, especially when
you're building an object from many variables.

2. Classes

JavaScript classes are a syntactic sugar over JavaScript's existing prototype-


based inheritance. They provide a much cleaner and more familiar way to create
"blueprints" for objects, allowing you to define constructor functions and
methods for objects that will be created from the class.

Think of a class as a template or a blueprint from which you can create many
similar objects.

2.1. Defining a Class


A class is defined using the class keyword.

2.2. Creating Instances (Objects) from a Class

You create new objects (called instances) from a class using the new keyword.
This calls the constructor method.

2.3. Class Inheritance (extends and super)

Classes support inheritance, allowing you to create new classes that are based
on existing ones. This is achieved using the extends keyword.

• extends: Used in a class declaration or class expression to create a


class that is a child of another class. The child class inherits
properties and methods from the parent (or "base") class.

• super(): Used inside the constructor of a child class to call the


constructor of the parent class. It's essential to call super() before
accessing this in the child's constructor.
2.4. Getters and Setters
You can define special methods called getters and setters to control how object
properties are accessed and modified.

2.5. Static Methods

Static methods are methods that belong to the class itself, not to instances of
the class. You call them directly on the class name. They are often used for
utility functions that don't require an instance of the class.
------------------------------------------------------------------------

Fetch API

------------------------------------------------------------------------

Import & Export Statements


import and export statements are used to work with Module Scope, which allow
you to organize and reuse code across different files.

Exporting Code:

The export keyword makes variables, functions, classes, or other values


available for use in other modules. There are two main types of exports:

• Named Exports: You can export multiple named values from a single module.
Alternatively, you can group named exports:

• Default Export: A module can have only one default export. This is often
used for the primary value or functionality provided by a module.

Importing Code:

The import keyword is used to bring exported values from other modules into the
current file.

• Importing Named Exports: You import named exports using curly


braces {} and specifying the names of the values to import.

You can also rename named imports using as:

To import all named exports into a single object (namespace):

• Importing Default Exports: You import a default export by giving it a


name directly, without using curly braces.
------------------------------------------------------------------------

Lexical Scope (Static Scope)

There are three primary types of scopes in JavaScript: Global Scope,


Function Scope, and Block Scope. These scopes determine the
accessibility of variables and functions in different parts of your
code. While Global, Function, and Block scope are the three main types,
there are a couple of other important scopes to understand in
JavaScript. The most crucial is Lexical Scope, which is the fundamental
rule that governs all other scopes. The other is Module Scope, which is
a modern addition for organizing code.
This isn't a separate type of scope like Global or Function, but rather
the principle that determines how all other scopes are nested and where
variables can be accessed.
Lexical scope means accessibility of variables is determined by their
physical location in the source code. A variable's scope is defined by
where it's declared, not where it's called or executed. It's also known
as "static scope."
• Lexical environment refers to nested Global, Block, and Function –
scopes. It is basically “A mapping between variable names and their
values, plus a link to the outer environment.”

Both blocks have their own lexical environments, and the inner one can
“see” the outer variables via the scope chain.

• Global Lexical Environment


Stores num = 3 and links to the outer environment (which is null
for global scope).
• Block Lexical Environment (inside if)
Exists, but has no variables of its own.
When console.log(num) runs, JS searches:
1. Block scope → not found
2. Global scope → found 3
Any scope where variables are stored (global, function, block) is a
lexical environment, even if it’s empty.

This is an example of lexical scoping, which describes how a parser


resolves variable names when functions are nested.
The word lexical refers to the fact that lexical scoping uses the
location where a variable is declared within the source code to
determine where that variable is available.
Nested functions have access to variables declared in their outer scope
(closures).
Closures
It is created when we have nested scopes and the inner scope
references/uses the variables (let, const, var, function) from outer
scope.
A closure is the combination of a function and the lexical environment
within which that function was declared.
OR
A closure is the combination of a function bundled together (enclosed)
with references to its surrounding state (the lexical environment). In
other words, a closure gives a function access to its outer scope
(Global, Block, or Function - scope).
In JavaScript, closures are created every time a function is created, at
function creation time.
The principle of Lexical Scope is what allows for closures, where an
inner function can access variables from its outer (parent) function,
even after the outer function has finished executing.
Closure variables are live references to the outer-scoped variable, not
a copy. This means that if you change the outer-scoped variable, the
change will be reflected in the closure variable, and vice versa, which
means that other functions declared in the same outer function will have
access to the changes.
The innerFunction's scope is lexically enclosed within outerFunction's
scope, so it has access to its parent's variables.
A closure happens when:
1. A function is defined inside another scope.
2. That inner function remembers the variables from where it was
created — even after that scope is gone.
In this case, myFunc is a reference to the instance of the function
displayName that is created when makeFunc is run. The instance of
displayName maintains a reference to its lexical environment, within
which the variable name exists. For this reason, when myFunc is invoked,
the variable name remains available for use, and "Mozilla" is passed to
console.log.

addTo5 and addTo10 both form closures. They share the same function body
definition, but store different lexical environments. In addTo5's
lexical environment, x is 5, while in the lexical environment
for addTo10, x is 10.
Example:
console.dir() is a method in the browser's developer console that
displays an interactive list of the properties of a specified JavaScript
object. It provides a detailed, tree-like representation of the object,
which is particularly useful for inspecting complex objects.
Practical Example:

Emulating private methods with closures


JavaScript, prior to classes, didn't have a native way of declaring
private methods, but it was possible to emulate private methods using
closures.
The following code illustrates how to use closures to define public
functions that can access private functions and variables.

There are 3 Lexical Environments in this code example. Normally, when


the IIFE finishes, #1 would disappear. But because the returned methods
(increment, decrement, value) still reference variables from #1, the JS
engine keeps #1 in memory — that’s the closure.

Those three public functions form closures that share the same lexical
environment (Function Lexical Environment for the IIFE).

An IIFE (“Immediately Invoked Function Expression”) always creates a


function scope that exists only for the lifetime of that function call —
but closures can keep it alive.
• Creates a new function scope (lexical environment).
• Variables (let, const, var, function) declared inside live in that
environment.
• Code outside the IIFE can’t directly access them.
That’s why they’re “private” — they’re not in the global scope.
We used an IIFE (Immediately Invoked Function Expression) so that:
1. privateCounter stays truly private
o The variable privateCounter lives inside the function’s
lexical environment, which is created when the IIFE runs.
o Because the IIFE executes immediately, it returns an object
with methods that close over (remember) that environment.
o No code outside of that function can directly access or change
privateCounter.
2. The closure is created once
o Without the IIFE, you’d just be declaring a function — the
closure wouldn’t be set up until you called it later.
o The IIFE runs immediately, returning an object whose methods
already have access to that single shared privateCounter.

That link between each method and the IIFE’s lexical environment is the
closure. Even after the IIFE finishes executing, the environment stays
alive because those methods still reference it.
The “private scope” is just the function’s lexical environment created
when the IIFE runs. It’s “private” because only code defined inside that
function can see its variables

Without IIFE — the problem


• This would work — but now you must call createCounter() to set up
the closure.
• The IIFE is just doing that setup automatically at the time of
definition.
• The IIFE runs immediately to create a private scope and return only
the methods you want exposed.

Multiple Closures
Multiple closures referencing to a single Lexical Environment.
The two counters maintain their independence from one another. Each
closure references a different version of the privateCounter variable
through its own closure. Each time one of the counters is called, its
lexical environment changes by changing the value of this variable.
Changes to the variable value in one closure don't affect the value in
the other closure.

Closure Scope Chain


A nested function's access to the outer function's scope includes the
enclosing scope of the outer function—effectively creating a chain of
function scopes.
------------------------------------------------------------------------

Module Scope
This is a modern scope introduced with ES6 Modules. It's similar to
Global Scope but is confined to a single file.
Every time you create a JavaScript file and use import/export, that file
creates its own module scope.
• Variables declared inside a module are private by default. They are
not accessible from the global scope or other modules.
• To share a variable, function, or class with other modules, you
must explicitly export it.
• To use something from another module, you must explicitly import
it.
This scope is vital for preventing global namespace pollution, allowing
developers to write self-contained and reusable code.
• File Paths:

When importing, you must specify the relative or absolute path to the module
file, including the .js extension (in browsers and Node.js with ES modules).

• type="module":

When using modules in a browser environment, the <script> tag that loads your
main JavaScript file must have type="module":

• Module Scope: Variables and functions within a module are scoped to that
module by default and are not globally accessible unless explicitly
exported.

------------------------------------------------------------------------

this keyword
Its value is not static; it's determined dynamically based on how a function is
called, not where it's declared.

Its value is assigned automatically when the code is executed. It always refers
to a single object—the "owner" of the function that's being executed.

The Four Rules of this

There are four primary rules that dictate what this refers to.

Global Context / Default Binding


When a function is called without a specific context (not as a method of an
object), this refers to the global object. In a browser, this is the window
object. In Node.js, it's the global object.

In strict mode, this is undefined in this context, which helps prevent


accidental access to the global object.
Object Method / Implicit Binding
When a function is called as a method of an object, this refers to the object
itself that the method is a part of. This is the most common and intuitive use
of this.

Consider the following example for this:


Example: this refers to the array which has been used to call the function
count().

Important Caveat: The value of this is set at the time of the function
call. If you "detach" the method from its object and call it, the
context is lost.
Constructor Call / new Binding
When a function is called with the new keyword, it acts as a
constructor. A new object is created, and this inside the constructor
function refers to this newly created object.

In modern JavaScript, classes and their constructor methods are a


cleaner way to handle this pattern, but the underlying principle is the
same.

Explicit Binding / call, apply, bind


You can explicitly and manually set the value of this using three
special methods available on all JavaScript functions: .call(),
.apply(), and .bind().
• call(): Calls a function with a specified this value and arguments
provided individually.
func.call(thisArg, arg1, arg2, ...)
• apply(): Calls a function with a specified this value and arguments
provided as an array.
func.apply(thisArg, [arg1, arg2, ...])
• bind(): Returns a new function with this permanently bound to a
specific object. It doesn't execute the function immediately.
const newFunc = func.bind(thisArg, arg1, ...)

Arrow Functions & this


Arrow functions (=>) handle the this keyword differently. They do not
have their own this context. Instead, they inherit the value of this
from the enclosing lexical scope (the scope they were defined in).
This behaviour is particularly useful inside object methods or event
handlers to avoid context issues.
If the setInterval callback were a regular function, this would default
to the global object, and this.count would not work as expected.

Order of Precedence
The four rules above have an order of precedence, from highest to
lowest:
1. Explicit Binding (.call, .apply, .bind)
2. new Binding
3. Implicit Binding (Object Method)
4. Default Binding (Global Context)
Arrow functions are a special case; they ignore all the above rules and
inherit this from their parent scope.

------------------------------------------------------------------------

Call, Apply, & Bind


These 3 are essential methods in JavaScript for controlling the this
keyword and function execution. They're all available on every function
in JavaScript and provide a way to explicitly set the context (this) for
a function call.
The Problem They Solve
As we've seen, the value of this is usually determined by how a function
is called. For example, a method on an object has this set to that
object. But what if you want to reuse a function from one object and
apply it to another? call, apply, and bind allow you to do exactly that.
They solve the problem of explicitly setting the this context when a
function is executed.

In this setup, the sayHello function is "detached" from any object. We


can use call, apply, or bind to tell it which object to use for this.

1. call()
The call() method executes a function immediately with a specified this
context. Arguments for the function are passed in one by one, separated
by commas.
• Syntax: functionName.call(thisArg, arg1, arg2, ...)
• Key Points:
o Executes Immediately: The function runs as soon as you call
.call().
o Arguments: You pass the arguments to the function
individually.

Method Borrowing
You can even use call() to borrow methods from other objects.

• objectWithMethod.methodName.call(borrowingObject, arg1, arg2, ...);


2. apply()
The apply() method is very similar to call(), with one key difference:
it expects the function's arguments to be passed as an array-like
object.
• Syntax: functionName.apply(thisArg, [arg1, arg2, ...])
• Key Points:
o Executes Immediately: Like call(), the function runs right
away.
o Arguments: All arguments must be bundled into an array.

A classic use case for apply() is when you have a function that accepts
multiple arguments, and you receive those arguments from an array or an
array-like object.
Note: In modern JavaScript (ES6+), the spread syntax (...) is often a
cleaner alternative to apply for passing array elements as arguments:
Math.max(...numbers).

3. bind()
The bind() method is fundamentally different from call() and apply()
because it does not execute the function immediately. Instead, it
returns a new function with its this context permanently bound to a
specified object.
• Syntax: const newFunc = functionName.bind(thisArg, arg1, arg2, ...)
• Key Points:
o Returns a New Function: It creates a new, reusable function
instance.
o Deferred Execution: The function doesn't run until you
explicitly call the new function.
o Arguments: Arguments can be passed at the time of binding or
when the new function is executed.

Example:
bind() is especially useful in event handling or when dealing with
callbacks, where a function might lose its original this context.

Example:

This output is generated after clicking on the button which suggests


that ‘this’ is referring to the html button rather than the class
‘Clicks’. To solve this issue, we use bind(thisArg):
Another example:

Same issue is seen here, we will use bind(thisArg):


Another Example:

------------------------------------------------------------------------

Currying
Currying is a functional programming technique where a function that
takes multiple arguments is transformed into a sequence of functions,
each taking a single argument.
The core idea is to break down a function call like f(a, b, c) into a
chain of function calls like f(a)(b)(c). This technique comes in-handy
when it is required to have all the arguments received and only then the
result should be returned.

Final sum is returned only when all the arguments are provided,
otherwise it will return one of the nested functions. curriedAdd(1)
returns a new function that expects the next argument (b), which in turn
returns another function that expects the final argument (c). This chain
of functions ultimately returns the result.

How to Curry Any Function


Writing curried functions manually can get tedious. You can write a
general-purpose curry utility function that takes a regular function and
returns a curried version of it.
The purpose of this curry function is to take any regular function, like
addThree, and return a new, "curried" version of it. The new function
can be called with a single argument at a time, or multiple arguments,
until it has received all the arguments it needs. Once it has enough
arguments, it executes the original function and returns the result.
The key to this behaviour is closures and recursion.
Arity refers to number of inputs.
------------------------------------------------------------------------

Partial Application
The real benefit of currying comes from partial application. This means
you can create new functions by calling the curried function with only
some of its arguments. The returned function "remembers" the arguments
it has already received. This is a form of a closure.

This allows you to create highly specialized, reusable functions from


more general ones.
A More Practical Example: Logging
Let's imagine you have a logging function that takes a logging level, a
timestamp, and a message.
With currying, you can create a specialized logger for each level:

Currying vs. Partial Application


The terms "currying" and "partial application" are often used
interchangeably, but there's a subtle distinction:
• Currying: Transforms a function f(a, b, c) into a sequence of
single-argument functions f(a)(b)(c).
• Partial Application: Takes a function and a fixed number of
arguments to return a new function that takes the remaining
arguments. It doesn't necessarily break down the function into a
chain of single-argument functions. The bind() method is a form of
partial application.
While they are distinct, they often serve the same purpose: to create
new, specialized functions from existing ones. Currying is a specific
technique for partial application.

------------------------------------------------------------------------

Function Composition
Function composition is a technique from functional programming where
you combine multiple functions to produce a new function. The output of
one function is automatically used as the input of the next, and so on.
Mathematically, if you have two functions f(x) and g(x), their
composition is written as (f ∘ g)(x), which means f(g(x)). In this
notation, you first apply g to x, and then apply f to the result.
In composition, each function expects a single value, which is the
output of the previous function.
A Basic Example: Without Composition

This works, but it can quickly become hard to read and debug with many
nested function calls.

Creating a compose Helper Function


To solve the readability issue and automate this process, we can create
a compose helper function.

A typical compose function takes any number of functions as arguments


and returns a new function that executes them from right-to-left.

With multiple inputs initially:


------------------------------------------------------------------------

Pure & Impure Functions


A pure function is a function that has two key characteristics:
1. It always returns the same output for the same input. The result of
the function call depends solely on the arguments passed to it.
2. It produces no side effects. A pure function does not modify any
external state, such as global variables, data on a server, or the
DOM. It does not cause any observable changes outside its scope,
such as modifying external state or performing I/O operations.
Think of a pure function as a mathematical function: f(x) = x + 1. If
you call f(5), you will always get 6. Nothing else in the universe
changes.

This function is pure because:


• Its output (5) is determined only by its inputs (2 and 3).
• It doesn't change anything outside of its scope. It simply returns
a new value.
An impure function is any function that violates one or both of the
rules of a pure function. It can be impure in two ways:
1. Its output depends on more than just its inputs. The function might
rely on a global variable, the current time, or a random number.
2. It has side effects. The function modifies something outside its
own scope.
Examples of common side effects include:
• Modifying a global variable.
• Writing to the DOM (e.g., document.getElementById('id').innerHTML =
'new text').
• Making an API call (e.g., fetch('api/data')).
• Logging to the console (e.g., console.log()).
• Mutating an input argument.
• Getting user input.
• Reading or writing a file.
• Math.random()
• new Date()
• File I/O
• Network Requests

Both increment and getTimestamp are impure. The increment function


changes the external count variable, and getTimestamp will return a
different value every time it's called, even with no inputs.
Advantages of Pure Functions
While impure functions are necessary for any real-world application (you
can't build a web page without side effects!), pure functions offer
significant benefits:
1. Predictability and Reliability: With the same inputs, you'll always
get the same output. This makes them easy to reason about and test.
You don't have to worry about external state affecting their
behavior.
2. Testability: You can easily test a pure function by providing
inputs and asserting that the output is what you expect. You don't
need to set up or tear down complex test environments.
3. Referential Transparency: This is a fancy term that means you can
replace a function call with its returned value without changing
the program's behavior. This allows for powerful optimizations by
compilers and interpreters.
4. Immutability: Pure functions often work with immutable data,
meaning they don't change their inputs. Instead, they return new
data, which helps prevent bugs caused by unexpected data mutations.
A Practical Example:

The pure version is safer and easier to track. You know for sure that
myOtherArray will not be altered, which prevents bugs in other parts of
your code that might rely on that array's original state.
The goal of functional programming is not to eliminate all impure
functions, but to isolate them. You should design your programs to have
a small, well-defined set of impure functions that handle side effects
(e.g., a function that fetches data, another that updates the UI). The
rest of your application's logic should be composed of predictable,
testable, and reusable pure functions.

------------------------------------------------------------------------

Shallow Copy & Deep Copy


A shallow copy creates a new object or array, but it only copies the
top-level properties. If any of the original properties contain
references to other objects (like arrays or nested objects), only the
references are copied, not the nested objects themselves. This means
that both the original and the new shallow-copied object will point to
the same nested objects in memory.
Methods for Shallow Copying
• Spread Syntax (...): The most common and modern way.

• Object.assign(): An older but still widely used method.

• Array.prototype.slice() and Array.from(): For arrays.


A deep copy creates a new object or array and recursively copies all
nested objects and their properties. The result is a completely
independent replica of the original. Any changes made to the deeply
copied object will not affect the original, and vice versa.
Methods for Deep Copying
• JSON.parse(JSON.stringify(obj)): The simplest and most common
method for a quick deep copy, but it has significant limitations.

Limitations of JSON methods:


o It loses methods/functions.
o It loses undefined values.
o It loses Map, Set, Date, and other complex types.
o It loses circular references (e.g., an object property that
refers back to the object itself).

• Structured Clone Algorithm (structuredClone): The modern, official,


and robust way to perform a deep copy. It's available in modern
browsers and Node.js. It handles a wide range of data types and
circular references.
------------------------------------------------------------------------

Web API
A Web API (Application Programming Interface) is a set of rules and
protocols for building and interacting with web-based software
applications. It defines how different software components can
communicate with each other over the internet, typically using HTTP.
There are two main categories of Web APIs:
• Browser APIs:
These are built-in functionalities provided by web browsers that allow
web applications to access and manipulate features of the browser and
the user's device. Examples include:
• DOM (Document Object Model) API: For interacting with the
structure and content of web pages.
• Geolocation API: For retrieving the user's geographical
location.
• Canvas API: For drawing graphics and animations on a web page.
• Web Storage API: For storing data locally in the browser
(e.g., localStorage, sessionStorage).
• Server APIs (or Web Service APIs):
These are APIs exposed by web servers that allow client applications
(like web browsers, mobile apps, or other servers) to request and
exchange data or trigger actions on the server. Examples include:
• RESTful APIs: A widely used architectural style for building
web services that leverage standard HTTP methods (GET, POST,
PUT, DELETE) for communication.
• GraphQL: A query language for APIs that provides a more
efficient and flexible way to fetch data compared to
traditional REST APIs.
• gRPC: A high-performance, open-source universal RPC framework.

------------------------------------------------------------------------

Event Loop
The Event Loop is a crucial component of JavaScript's concurrency model.
It's the mechanism that allows JavaScript, a single-threaded language,
to perform non-blocking asynchronous operations. Instead of freezing the
entire program while waiting for a long task to finish (like fetching
data from a server), the Event Loop offloads that task and lets the rest
of the code run.
The Key Components
To understand the Event Loop, you need to know its three main parts:
1. Call Stack (or "Execution Stack"): This is where JavaScript keeps
track of all the functions being executed. When a function is
called, it's pushed onto the stack. When it returns, it's popped
off. JavaScript can only execute one function at a time in the Call
Stack.
2. Web APIs (or "Browser APIs"): These are capabilities provided by
the browser, not by the JavaScript engine itself. They handle
asynchronous tasks like:
o setTimeout()
o setInterval()
o fetch() (for network requests)
o DOM events (click, scroll, etc.) When a function from a Web
API is called in your code, it's offloaded from the Call Stack
and handled by the browser.
3. Callback Queue (or "Task Queue"): This is a waiting list for all
the callback functions that are ready to be executed. When an
asynchronous task (like a setTimeout timer) is complete in Web API,
its associated callback function is pushed onto this queue. It
holds asynchronous tasks that are ready to be executed. These tasks
include:

• Timers: setTimeout() and setInterval() callbacks.

• I/O Operations: Network requests, like fetch(), or file system


operations (in Node.js).

• User Events: Click, keypress, and other DOM events.

4. The Microtask Queue


The Microtask Queue is a special queue with a higher priority than the
Task Queue. It holds asynchronous tasks that need to be executed as soon
as the current function on the Call Stack finishes, but before the Event
Loop checks the Task Queue.
Microtasks are primarily created by:
• Promises: The .then(), .catch(), and .finally() methods.
• async/await: The code after an await is treated as a microtask.
• queueMicrotask(): A dedicated method for creating microtasks.
How the Event Loop Works With Task Queue
The Event Loop is a simple, continuously running process. Its sole job
is to monitor two things: the Call Stack and the Callback Queue.
The core rule of the Event Loop is: If the Call Stack is empty, it takes
the first item from the Callback Queue and pushes it onto the Call
Stack.

1. "1. Start" is pushed onto the Call Stack, executed, and popped off.
2. setTimeout() is pushed onto the Call Stack. It's a Web API, so the
browser handles the 2-second timer. The setTimeout() function is
then popped off the Call Stack, and the JavaScript engine
continues.
3. "2. End" is pushed onto the Call Stack, executed, and popped off.
At this point, the Call Stack is now empty. The main thread is
free.
4. Meanwhile, the browser's timer for setTimeout is running in the
background. After 2 seconds, the timer expires.
5. The browser pushes the setTimeout's callback function (() => {
console.log("3. Inside setTimeout"); }) onto the Callback Queue.
6. The Event Loop constantly checks if the Call Stack is empty. Since
it is, it sees the callback function waiting in the queue.
7. The Event Loop moves the callback function from the Queue to the
Call Stack.
8. The callback function is executed, "3. Inside setTimeout" is
logged, and the function is popped off the stack.
The process repeats. The Event Loop ensures that all asynchronous
callbacks are executed, but only when the main thread is idle,
preventing the application from freezing.

How the Event Loop Works With Microtask Queue


The full cycle of the Event Loop is:
1. All synchronous code executes completely (execute all functions on
the Call Stack until it's empty).
2. Process all pending tasks in the Microtask Queue.
3. Render the UI (if needed).
4. Take one single task from the Microtask Queue and push it onto the
Call Stack.
5. Go back to step 1 and repeat until Microtask Queue is empty.
This priority system means that a promise's callback will always execute
before a setTimeout callback if they are scheduled at the same time.

Execution Flow:
1. console.log("A") is executed.
2. setTimeout() schedules its callback to the Task Queue.
3. Promise.resolve().then() schedules its callback to the Microtask
Queue.
4. console.log("D") is executed.
5. The Call Stack is now empty.
6. The Event Loop checks the Microtask Queue. It finds
console.log("C") and pushes it onto the stack. C is logged.
7. The Microtask Queue is now empty.
8. The Event Loop checks the Task Queue. It finds console.log("B") and
pushes it onto the stack. B is logged.
The final output is A, D, C, B.

The Importance of the Event Loop


• Non-Blocking I/O: It allows JavaScript to perform time-consuming
tasks like API calls and file I/O without blocking the user
interface. This is why a webpage remains responsive even while it's
fetching data in the background.
• Concurrency Model: It provides a simple yet effective model for
handling concurrency in a single-threaded environment.
• Predictable Execution: It guarantees that queued callbacks will not
interrupt currently executing code on the Call Stack.
Example:
------------------------------------------------------------------------

Debouncing & Throttling


These are two common techniques used to optimize performance and control
the frequency of function calls, especially in response to events that
fire rapidly, like resizing a window, scrolling, or typing in a search
bar.

Debouncing
Debouncing is a technique that ensures a function is called only after a
specific period of inactivity. It's like waiting for a user to finish an
action before doing something.
Think of it this way: when you're in an elevator, the doors don't close
immediately after someone steps in. The elevator waits for a moment to
see if anyone else is coming. If someone else gets on, the timer resets.
The doors only close after a period of time has passed with no new
activity.
How it works:
• A debounce function takes the original function and a delay time as
arguments.
• When the event fires, it clears any previously set timer and starts
a new one.
• The original function is only executed when the timer completes
without being reset.
Best for: Events that you only want to handle once, after the user has
"finished" their action.
• Examples:
o Search bar input: Only trigger the API call after the user
stops typing for a few hundred milliseconds.
o Window resizing: Only recalculate layout once the user has
finished resizing the window.

Throttling
Throttling is a technique that limits the number of times a function can
be called within a given time period. It's like a bouncer at a club who
only lets one person in every few seconds, regardless of how many people
are waiting in line.
How it works:
• A throttle function takes the original function and a time interval
as arguments.
• It sets a timer and executes the function immediately on the first
event.
• It then blocks any subsequent calls and saves the new changes until
the timer completes. Once the timer is done, it applies the
updates, resets, and is ready for the next call.
Best for: Events that fire constantly, where you want to execute a
function at a steady, limited rate.
• Examples:
o Scrolling: Show an animation or load more content every 200ms,
not every millisecond.
o Progress bars: Update a progress bar at a fixed interval to
avoid over-rendering.
o Gaming: Limit the rate of a game character's action, like
firing a weapon.

Key Differences: Debounce vs. Throttle

Feature Debounce Throttle

Group a series of events Limit the number of times a


Goal into a single, final function can be called over a
function call. time period.

After a period of
Execution At a regular, fixed interval.
inactivity.

Elevator doors: waits until Bouncer: lets someone in every X


Analogy
no one is coming. seconds.

Search bars, window resize, Scrolling, button clicks, game


Use Cases
form validation. actions.
Example:

In this example, upon moving the mouse on the web page, the function
incrementCount() is called every pixel change. Without Debounce and
Throttle, we can see the function was called 503 times. While the
Debounce was called after the mouse stopped moving for the specified
delay (1 second) and the Throttle was called every time after the
specified delay (1 second).
Example:
------------------------------------------------------------------------

Web Storage API

Local Storage (Permanent)


Local Storage is used to store data with no expiration date. The data
persists even when the user closes the browser window or tab, and it is
available across multiple tabs and windows from the same origin. It's
ideal for data that needs to be stored for a long time, such as user
preferences, theme settings, or a shopping cart.
• Persistence: Data persists until it is explicitly deleted by the
user or by the website's code.
• Scope: It's limited to the origin, which is the combination of the
protocol (http or https), domain (www.example.com), and port
number. Data stored on one subdomain, like blog.example.com, is not
accessible on shop.example.com.
• Capacity: Most browsers provide a capacity of around 5-10 MB.
• Data Type: It only stores strings. You must manually convert
complex data types like objects or arrays to strings using
JSON.stringify() before storing them and parse them back using
JSON.parse() when retrieving them.

Session Storage (Temporary)


Session Storage is similar to local storage, but its data is only
available for the duration of a single session.
Data is not destroyed upon reloading a page. Session storage is designed
to persist data for the duration of a user's session within a single
browser tab or window. This includes page reloads and restores. The data
stored in sessionStorage will only be cleared when the user explicitly
closes that specific browser tab or window. Each tab or window maintains
its own independent session storage.
• Persistence: Data is cleared when the page session ends (when the
tab is closed).
• Scope: It's also limited by origin, but it's even more restrictive—
data is isolated to the specific browser tab where it was created.
If you open the same URL in a new tab, it creates a new, empty
session storage instance.
• Capacity: The capacity is similar to local storage, typically 5-10
MB.
• Data Type: Like local storage, it only stores strings and requires
JSON.stringify() and JSON.parse() for more complex data types.

Cookies (ID Card)


Cookies are a fundamental part of the HTTP protocol. Because HTTP is a
stateless protocol (it has no memory of past requests), cookies were
created to provide a way for websites to "remember" information about a
user between requests. They are sent with every subsequent request to
the same domain.
Think of a cookie as a small, personalized ID card. When you visit a
website, the server gives you an ID card (the cookie) to store in your
browser. Every time you return to that site, your browser automatically
sends the ID card back, so the server can recognize you and "remember"
things about you.

Types and Common Uses


Cookies are used for a variety of purposes:
1. Session Management: To keep a user logged in as they navigate a
website. This is why you don't have to re-enter your login details
on every page. These are often called session cookies and typically
expire when the browser is closed.
2. Personalization: To remember user preferences, such as language
settings, theme choices (e.g., dark mode), or customized layouts.
3. Tracking and Analytics: To track a user's browsing activity across
a site or even across different sites. This is commonly used for
advertising and marketing purposes. These are often called tracking
cookies or third-party cookies.

How Cookies Work


Here's the basic flow of a cookie's life cycle:
1. Server Sends a Cookie: A server responds to an HTTP request and
includes a Set-Cookie header in the response. This header tells the
browser to store the cookie.
2. Browser Stores the Cookie: The browser receives the response and
stores the cookie as a small text file.
1. Browser Sends the Cookie Back: For all subsequent requests to that
same domain, the browser automatically includes the stored cookie
in the Cookie header of the request.

Key Attributes and Parameters


When a server sets a cookie, it can define several parameters that
control the cookie's behavior and security.
• Expires or Max-Age: This sets a specific date or a duration for the
cookie's expiration. If not set, it's a session cookie that expires
when the browser is closed.
• Domain: Specifies which domains can receive the cookie. If not
specified, it defaults to the host domain.
• Path: Specifies the URL path for which the cookie is valid. For
example, a cookie with Path=/app will only be sent for requests to
URLs under the /app path.
• Secure: The cookie will only be sent to the server over an
encrypted HTTPS connection.
• HttpOnly: Prevents client-side scripts (like JavaScript) from
accessing the cookie. This is a crucial security measure to prevent
Cross-Site Scripting (XSS) attacks, where a malicious script could
steal a user's cookie.
• SameSite: Controls whether the cookie is sent with cross-site
requests. This is a modern security feature to prevent Cross-Site
Request Forgery (CSRF) attacks. Values can be Strict, Lax, or None.

------------------------------------------------------------------------

Appendix
REPL
The Read-Evaluate-Print Loop (REPL) in Node.js provides an interactive
environment for executing JavaScript code that takes single expression
as user input. It's a convenient tool for testing code snippets,
debugging, and exploring the language.
Starting the Node.js REPL:
Open your terminal or command prompt and Type node and press Enter.
This will launch the Node.js REPL, indicated by a > prompt. You can then
type JavaScript code directly into the terminal and press Enter to
execute it. The REPL will evaluate the code and print the result.
Key Features and Usage:
• Interactive Execution:
Enter JavaScript expressions, statements, or even multi-line code
blocks, and see the results immediately.
• Variable Declarations:
Declare variables using var, let, or const as you would in a regular
Node.js script.
• Special Variable _:
The _ (underscore) variable stores the result of the last evaluated
expression, allowing you to chain operations.
• Exiting the REPL:
Press Ctrl+C twice or type .exit and press Enter to exit the REPL
session.
• Dot Commands:
The REPL provides special "dot commands" for various functionalities,
such as .help for a list of commands, .clear to reset the current
context, and .load to load a JavaScript file.
IEFI
IIFE (Immediately Invoked Function Expression): An IIFE is a JavaScript
function that runs as soon as it is defined.

1. Isolate Scope: Before let and const and modules, IIFEs were the primary
way to create a private scope for variables, preventing them from
polluting the global scope. This is still a valid use case for older
codebases or when you specifically want a temporary, isolated execution
context.

2. Execute Code Immediately: They run as soon as they are encountered in the
script OR it is called immediately as soon as it is defined.

3. Encapsulation: They can enclose variables and functions, making them


private to the IIFE's scope.

These functions cannot be called and only execute once.

Spread Syntax (...)


1. Spread Syntax with Arrays (Expanding Iterables)

When used with arrays (or any iterable like strings, Maps, Sets), the spread
syntax expands the iterable into its individual elements.

a) Copying Arrays

This is a common and clean way to create a shallow copy of an array.

Otherwise, it creates a deep copy.


But there is a catch, shallow copy is only created when array is linear,
otherwise if nested or 2d, it does not perform shallow copy on nested array
elements rather it references them.

b) Combining/Concatenating Arrays

You can easily merge multiple arrays.

c) Adding Elements to an Array (Immutably)

Useful for adding elements without modifying the original array (common in
React state updates).

d) Passing Array Elements as Function Arguments

When you need to pass elements of an array as separate arguments to a function.


This is often used with Math methods.
e) Converting NodeList/HTMLCollection to Array

document.querySelectorAll() returns a NodeList, which is array-like but not a


true array. Spread syntax can convert it.

f) Spreading Strings

A string is an iterable, so you can spread it into an array of characters.

2. Spread Syntax with Objects (Object Rest/Spread Properties)

When used with objects, the spread syntax copies properties from one object
into another. This is a shallow copy.

a) Copying Objects

Creates a shallow copy of an object.

b) Merging/Combining Objects

Combines properties from multiple objects. If there are duplicate keys, the
property from the last object in the spread will "win" (override previous
ones).
c) Updating Object Properties (Immutably)

Common pattern in state management (e.g., Redux, React useState).

Random Number Generator


In JavaScript, random numbers are primarily generated using
the Math.random() method.

1. Generating a random floating-point number between 0 (inclusive) and 1


(exclusive):

2. Generating a random integer within a specific range (inclusive of both min


and max):

To generate a random integer between a min and max value (inclusive), use the
following formula:
Explanation of the formula:

• Math.random(): Generates a floating-point number between 0 (inclusive)


and 1 (exclusive).

• (max - min + 1): Calculates the size of the range, including


both min and max. Adding 1 ensures that max is also a possible outcome
after Math.floor().

• Math.random() * (max - min + 1): Scales the random number to the desired
range size.

• Math.floor(...): Rounds the result down to the nearest whole number,


making it an integer.

• + min: Shifts the result to start from the specified min value.

Important Note: Math.random() generates pseudo-random numbers, meaning they are


generated by an algorithm and are not truly random. For cryptographic or
security-sensitive applications, consider using crypto.getRandomValues() from
the Web Crypto API for more secure random number generation.

------------------------------------------------------------------------

Random Color Generator


HEX Colors
This is a popular method because HEX codes are widely used in CSS. A HEX color
is a 6-digit hexadecimal number, like #RRGGBB.
How it works:

• Math.random() gives you a float between 0 (inclusive) and 1 (exclusive).

• Multiplying by 16777215 (which is 0xFFFFFF in decimal) and Math.floor()


gives you a random integer within the full range of possible HEX colors.

• .toString(16) converts that integer into its hexadecimal string


representation.

• .padStart(6, '0') ensures the string is always 6 characters long by


adding leading zeros if needed (e.g., ff becomes 0000ff).

RGB Colors
RGB colors are defined by the intensity of Red, Green, and Blue, each ranging
from 0 to 255.

How it works:

• We simply generate three separate random numbers between 0 and 255 for
the Red, Green, and Blue components.

• Then, we use a template literal (the backticks `) to format them into the
standard rgb(R, G, B) string.
You could also generate RGBA colors (Red, Green, Blue, Alpha) by adding a
fourth random value for a (alpha, opacity) between 0 and 1:

HSL Colors
HSL (Hue, Saturation, Lightness) is another color model that can sometimes feel
more intuitive for generating visually pleasing random colors, especially if
you want to vary just the "color" while keeping saturation/lightness
consistent.

• Hue: 0-360 degrees (represents the color itself, like red, green, blue).

• Saturation: 0-100% (how vibrant the color is).

• Lightness: 0-100% (how bright or dark the color is).

How it works:

• Generate random numbers within the appropriate ranges for hue,


saturation, and lightness.

• Format them into the hsl(H, S%, L%) string.

------------------------------------------------------------------------

Time Out & Time Interval


1. setTimeout() (Time Out)

setTimeout() is used to execute a function once after a specified delay (in


milliseconds).
• functionToExecute: The function (or code string) you want to run.

• delayInMilliseconds: The time (in milliseconds) to wait before executing


the function. (1000ms = 1 second).

• [arg1, arg2, ...]: (Optional) Arguments that will be passed to


functionToExecute when it runs.

Clearing setTimeout() with clearTimeout()

setTimeout() returns a unique ID (a number) for the timer. If you want to


cancel the scheduled execution before the delay has elapsed, you use
clearTimeout() with that ID.

2. setInterval() (Time Interval)

setInterval() is used to execute a function repeatedly at a fixed interval. It


continues to run until you explicitly stop it using clearInterval().
• functionToExecute: The function you want to run repeatedly.

• intervalInMilliseconds: The time (in milliseconds) between each


execution.

Clearing setInterval() with clearInterval()

Like setTimeout(), setInterval() returns a unique ID. To stop the repeating


execution, you must call clearInterval() and pass it the ID returned by
setInterval().

Notes on Timers

1. Asynchronous Behavior: Both setTimeout and setInterval are asynchronous.


They do not pause the execution of the main JavaScript thread. The code
following the timer setup will run immediately, and the timed function
will be executed later, when the timer expires and the JavaScript event
loop is free.

2. Delay is Minimum, Not Guaranteed: The delay you specify in setTimeout or


setInterval is a minimum delay. JavaScript is single-threaded, and the
function will only execute when the call stack is empty. If the browser
is busy running other code, the actual execution might be slightly
delayed.
3. Memory Leaks (The Importance of Clearing): When you use setInterval, the
function you schedule remains in memory and continues to run until it is
explicitly cleared. If you stop needing the functionality (e.g., a user
navigates away from a page component that set the interval), but you
forget to call clearInterval(), you can cause a memory leak and
unnecessary processing, as the function keeps running in the background.

------------------------------------------------------------------------

Type Coercion
Explicitly converting data to different types is often called type
coercion or type casting. You can achieve this using various built-in
functions and operators.

Converting to String
You can convert almost any data type to a string.
• String() constructor: This is a global function that converts the
value to a string. It works for all primitive types and objects.
• .toString() method: Most data types (numbers, booleans, arrays,
objects, etc.) have a toString() method.

o Note: null and undefined do not have a .toString() method and


will throw an error if you try to call it directly.
String(null) and String(undefined) work correctly, returning
"null" and "undefined" respectively.
• String Concatenation (Implicit Coercion): While not strictly
explicit, concatenating with an empty string is a common trick that
forces conversion to a string.

Converting to Number
Converting to a number can be a bit trickier, as non-numeric values
might result in NaN (Not a Number).
• Number() constructor: This global function converts various types
to numbers.
• parseInt() and parseFloat(): These functions are specifically for
converting strings to integers and floating-point numbers,
respectively. They are more forgiving than Number() as they parse
until they encounter a non-numeric character.

• Unary Plus Operator (+): This is a shorthand for converting a value


to a number. It performs the same conversion as Number().

Converting to Boolean
• Boolean() constructor: This is the most explicit way to convert a
value to a boolean.
• Double NOT Operator (!!): This is a common and concise way to
convert a value to its boolean equivalent. The first ! converts to
a boolean and then inverts it; the second ! inverts it back.

Truthy & Falsy Values


Truthiness and Falsiness: In JavaScript, values that are not explicitly true or
false can still be evaluated as "truthy" or "falsy" in a boolean context (like
if statements or with logical operators).

Common Falsy Values:

• false

• 0 (the number zero)

• -0 (negative zero)

• 0n (BigInt zero)

• "" (empty string)

• null

• undefined

• NaN (Not-a-Number)

Common Truthy values: All other values are considered truthy

• "hello" (any non-empty string)


• 13 (any non-zero number)
• {} (any curly-braces block)
• [] (any square-brackets block)
• function(){} (any function)

Example:

Empty square-brackets are truthy value, but since it is being compared


with a different data type (boolean), JavaScript Engine first converts
left-hand side to a string:

Still data types are different on both side, so now JavaScript Engine
converts both into number data type:

Example:
• A crucial and unique characteristic of NaN is that it is the only
value in JavaScript that is not strictly equal to itself. That
is, NaN === NaN evaluates to false. This also applies to loose
equality (NaN == NaN is also false).

------------------------------------------------------------------------

References
https://medium.com/javascript-scene/10-interview-questions-every-
javascript-developer-should-know-in-2024-c1044bcb0dfb
https://www.youtube.com/watch?v=jnME98ckDbQ&list=PL1PqvM2UQiMoGNTaxFMSK2
cih633lpFKP&ab_channel=ColorCode

------------------------------------------------------------------------

You might also like