Javascript_Notes
Javascript_Notes
1. History of Javascript
1995: JavaScript was created in just 10 days by Brendan Eich at Netscape. Originally called Mocha, then
LiveScript, and finally JavaScript (partly for marketing, to ride on Java's popularity).
1996: Microsoft created a JavaScript variant called JScript for Internet Explorer, leading to early browser
incompatibilities.
1997: JavaScript was standardized by ECMA International as ECMAScript (ES1) to ensure consistency across
browsers.
2005: AJAX became popular (asynchronous JavaScript and XML), enabling dynamic, responsive web apps
like Google Maps and Gmail.
2015: Major update ECMAScript 6 (ES6) introduced modern features like let , const , arrow functions, classes,
promises, and modules.
Today: JavaScript is a core technology of the web (alongside HTML and CSS), used in front-end and back-
end development, mobile apps, and even desktop applications.
🔹 1. Interactivity
Adds interactive features like dropdowns, sliders, modal windows, and form validation.
Responds to user actions (clicks, hovers, keystrokes) without reloading the page.
Can show/hide elements, change styles, or modify content based on logic or user input.
🔹 3. Asynchronous Communication
Uses AJAX or Fetch API to load data from servers without refreshing the page.
Enables real-time updates (e.g., live chat, weather apps, stock tickers).
🔹 4. Client-Side Logic
Handles tasks in the browser like calculations, decision-making, and data validation.
🔹 5. Back-End Development
With Node.js, JavaScript is also used for server-side programming.
Enables full-stack development using a single language across front-end and back-end.
Javascript Notes 1
3. Variables in javascript
Variable
In JavaScript, a variable is a named storage location for data. Think of it like a container or a box that holds a
value. You give this container a name so you can refer to it later in your code and either put data into it or retrieve
data from it.
Declaration: Before you can use a variable, you typically need to declare it. In modern JavaScript, you use the
keywords let or const for this purpose. (Historically, var was used, but let and const are generally preferred
now due to their better scoping behavior.)
let : Declares a block-scoped local variable, optionally initializing it to a value. The value can be reassigned
later.
reassigned.
Assignment: After declaring a variable, you can assign a value to it using the assignment operator ( = ).
Data Types: Variables in JavaScript are dynamically typed. This means you don't have to specify the data
type (e.g., number, string, boolean) when you declare a variable. The variable can hold different types of
values at different times during the program's execution.
Scope: This refers to where in your code a variable is accessible.11 let and const have block scope (meaning
they are only accessible within the block of code where they are defined), while var has function scope.
Purpose: Variables are fundamental for storing and manipulating data in your programs. They allow you to:
Example of a variable:
let userName = "Alice"; // Declaring a variable named userName and assigning it a string value
let age = 30; // Declaring a variable named age and assigning it a number value
const PI = 3.14159; // Declaring a constant named PI
1. var (Legacy/Function-Scoped)
Scope: var is function-scoped or globally scoped. This means:
If declared outside any function, it's globally accessible (and becomes a property of the global object, like
window in browsers).
var does not respect block scope (e.g., if blocks, for loops).
Hoisting: var declarations are hoisted to the top of their containing function or global scope. This means you
can use a var variable before its actual declaration in the code, but its initial value will be undefined .
Javascript Notes 2
Reassignment: You can reassign a value to a var variable.
Redeclaration: You can redeclare a var variable within the same scope without an error. This can lead to
unexpected behavior and bugs.
// Global scope
var globalVar = "I'm global";
function exampleVar() {
console.log(globalVar); // Output: I'm global (accessible from global scope)
if (true) {
var x = 10; // function-scoped, not block-scoped
console.log(x); // Output: 10
}
var y = 20;
console.log(y); // Output: 20
exampleVar();
// console.log(x); // ReferenceError: x is not defined (x is function-scoped to exampleVar)
2. let (Modern/Block-Scoped)
Scope: let is block-scoped. This means a let variable is only accessible within the block of code (defined by
{} ) where it is declared. This includes if statements, for loops, while loops, and standalone blocks.
Hoisting: let declarations are also hoisted, but they are not initialized with undefined . Instead, they are in a
"temporal dead zone" from the start of the block until their declaration is processed. Accessing a let variable
before its declaration will result in a ReferenceError .
Redeclaration: You cannot redeclare a let variable within the same scope. Attempting to do so will result in a
SyntaxError .
function exampleLet() {
// console.log(a); // ReferenceError: Cannot access 'a' before initialization (temporal dead zone)
let a = 10;
console.log(a); // Output: 10
if (true) {
let b = 20; // Block-scoped to this if block
console.log(b); // Output: 20
}
Javascript Notes 3
let c = 30;
// let c = 40; // SyntaxError: Identifier 'c' has already been declared (redeclaration not allowed)
c = 40; // Reassignment is allowed
console.log(c); // Output: 40
}
exampleLet();
Hoisting: Similar to let , const declarations are hoisted but are in a "temporal dead zone." Accessing a const
Reassignment: You cannot reassign a value to a const variable after its initial assignment. It must be initialized
when declared.
Redeclaration: You cannot redeclare a const variable within the same scope. Attempting to do so will result in
a SyntaxError .
Important Note on const with Objects/Arrays: While a const variable cannot be reassigned to a new value (or
a new object/array), if the value it holds is an object or an array, the contents (properties of the object or
elements of the array) can still be modified. The const keyword only prevents the variable from pointing to a
different memory address.
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable. (reassignment not allowed)
function exampleConst() {
// console.log(city); // ReferenceError: Cannot access 'city' before initialization
const city = "Mumbai";
console.log(city); // Output: Mumbai
if (true) {
const country = "India"; // Block-scoped
console.log(country); // Output: India
}
// const city = "Delhi"; // SyntaxError: Identifier 'city' has already been declared (redeclaration not allowed)
// myArray = [5, 6]; // TypeError: Assignment to constant variable. (reassignment of the array itself is not allowe
// myObject = { name: "Priya" }; // TypeError: Assignment to constant variable. (reassignment of the object itsel
}
exampleConst();
Summary Table:
Javascript Notes 4
Feature var let const
Hoisting Yes (initialized undefined ) Yes (temporal dead zone) Yes (temporal dead zone)
Redeclaration Yes No No
Best Use Case Legacy code only Variables that will change Variables that should remain constant
Use const by default: If you know a variable's value will not change after its initial assignment, use const . This
improves code readability and helps prevent accidental modifications.
Use let when reassignment is needed: If you anticipate that a variable's value will need to change during the
execution of your code (e.g., loop counters, values that are updated based on conditions), then use let .
By following these best practices, you'll write cleaner, more predictable, and less error-prone JavaScript code.
Number :
Includes special values like Infinity (positive infinity, e.g., 1 / 0 ), Infinity (negative infinity, e.g., (- 1) / 0 ), and
NaN (Not-a-Number, indicating an invalid or unpresentable numerical result, e.g., "hello" / 2 ).
All numbers in JavaScript are essentially stored as double-precision 64-bit floating-point numbers.
Example: let age = 30; , let price = 19.99; , let bigNum = 1.2e+5;
String :
Can be enclosed in single quotes ( '...' ), double quotes ( "..." ), or backticks ( `...` ).
Backticks allow for template literals, which enable embedding expressions (variables, functions, etc.)
directly within the string using ${} .
Example: let name = "Alice"; , let greeting = 'Hello, world!'; , let message = My name is ${name}.``
Boolean :
Undefined :
Represents a variable that has been declared but has not yet been assigned a value.
Javascript Notes 5
Null :
It's a primitive value, despite typeof null returning 'object' (which is a long-standing bug in JavaScript).
Symbol :
Often used to create unique object property keys to avoid name clashes.
BigInt :
Introduced in ES2020.
Represents whole numbers larger than 2^53 - 1 (the maximum safe integer that Number can represent).
Object :
Keys are strings (or Symbols), and values can be any data type (including other objects).
Example: JavaScript
let person = {
firstName: "John",
lastName: "Doe",
age: 30,
isStudent: false
};
Arrays are a special type of object where the keys are numeric indices (0, 1, 2, ...).
Functions are also a special type of object in JavaScript (they are "first-class citizens" and can be treated
like any other value).
Other built-in objects like Date , RegExp , Map , Set , etc., also fall under the Object type.
typeof Operator
You can use the typeof operator to check the data type of a variable or a value:
JavaScript
Javascript Notes 6
console.log(typeof Symbol('id')); // "symbol"
console.log(typeof 123n); // "bigint"
console.log(typeof {}); // "object"
console.log(typeof []); // "object" (arrays are objects)
console.log(typeof function(){}); // "function" (functions are objects)
A. Number
1. The Number Type (Floating-Point Numbers)
Single Numeric Type: Unlike languages like C++ or Java that have separate types for integers ( int , long ) and
floating-point numbers ( float , double ), JavaScript has only one numeric type: Number . This Number type is used
for both whole numbers and numbers with decimal points.
IEEE 754 Double-Precision: All JavaScript numbers are internally represented as 64-bit floating-point
numbers following the IEEE 754 standard (specifically, double-precision format). This is the same as the
double type in C++ or Java.
11 bits for the exponent (to handle very large or very small numbers).
52 bits for the mantissa (also called significand, representing the significant digits of the number).
Integer Precision: Due to this floating-point representation, JavaScript can safely represent integers only
within a specific range: from −(253−1) to 253−1.JavaScript
Number.MAX_SAFE_INTEGER (253−1=9,007,199,254,740,991)
Number.MIN_SAFE_INTEGER (−(253−1)=−9,007,199,254,740,991)
Integers outside this "safe" range might lose precision, meaning that numbers that should be different
might be represented as the same value.
console.log(9007199254740991); // 9007199254740991
console.log(9007199254740991 + 1); // 9007199254740992
console.log(9007199254740991 + 2); // 9007199254740992 (!!! precision loss)
Floating-Point Precision Issues: A common gotcha with floating-point numbers (not just in JavaScript, but in
many programming languages that use this standard) is that some decimal fractions cannot be represented
exactly in binary. This can lead to seemingly odd results in calculations: JavaScript
For financial calculations or when exact decimal precision is critical, it's often recommended to:
Work with integers: Convert values to cents (or other smallest units) to perform calculations and then
convert back for display.
Use a BigInt: For integers beyond the safe range (see below).
Use a third-party library: Libraries like decimal.js , bignumber.js , or big.js provide arbitrary-precision arithmetic
for decimal numbers.
Infinity: Represents mathematical infinity (infty). You get this when a number exceeds the largest representable
finite number.
Javascript Notes 7
Infinity : Represents negative infinity (−infty).
NaN (Not-a-Number): Represents a computational error or an undefined mathematical operation. It's unique
in that NaN is not equal to itself ( NaN === NaN is false ).JavaScript
Arbitrary Magnitude: BigInt can represent integers of arbitrary precision, meaning they can hold numbers far
larger than Number.MAX_SAFE_INTEGER .
Declaration: You create a BigInt by appending n to an integer literal or by using the BigInt() constructor.
Cannot Mix with Number : You generally cannot mix BigInt and Number types directly in arithmetic operations.
You'll need to explicitly convert one to the other.
// console.log(10 + 10n); // TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(10 + Number(10n)); // OK: 20
console.log(BigInt(10) + 10n); // OK: 20n
Octal (Base 8): Prefixed with 0o or 0O (strict mode requires this, older non-strict mode might interpret 0
1.2e5 (120000)
3e-4 (0.0003)
Javascript Notes 8
Number.isNaN(value) : Checks if a value is NaN . (More reliable than value === NaN ).
parseInt(string, radix) : Parses a string argument and returns an integer of the specified radix (base).
JavaScript
console.log(Number.isFinite(10)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isInteger(5)); // true
console.log(Number.isInteger(5.5)); // false
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("hello")); // false (because "hello" is not NaN itself)
console.log(parseInt("100px")); // 100
console.log(parseFloat("3.14abc")); // 3.14
B. String
1. Creating Strings (String Literals)
You can create strings using three types of quotes:
Multi-line strings: You can write strings spanning multiple lines without needing escape characters.
String Interpolation: You can embed expressions (variables, function calls, arithmetic operations, etc.)
directly within the string using the ${} syntax.
Javascript Notes 9
let price = 10;
let quantity = 3;
let total = `The total cost is ${price * quantity} INR.`;
console.log(total); // Output: The total cost is 30 INR.
2. String Immutability
One of the most important concepts about JavaScript strings is that they are immutable. This means that once a
string is created, its content cannot be changed.
When you perform operations that seem to "modify" a string (like toUpperCase() or replace() ), what actually
happens is that a new string is created with the desired changes, and the original string remains untouched in
memory.
If you reassign a variable to a "modified" string, you're simply pointing the variable to a new string in memory.
Common Properties:
length : Returns the number of characters in the string.
Common Methods:
Accessing Characters:
Changing Case:
indexOf(substring) : Returns the index of the first occurrence of a substring, or 1 if not found.
lastIndexOf(substring) : Returns the index of the last occurrence of a substring, or 1 if not found.
includes(substring) : Returns true if the string contains the substring, false otherwise (case-sensitive).
Javascript Notes 10
let sentence = "The quick brown fox jumps over the lazy dog.";
console.log(sentence.indexOf("fox")); // Output: 16
console.log(sentence.includes("dog")); // Output: true
console.log(sentence.startsWith("The")); // Output: true
console.log(sentence.endsWith("dog.")); // Output: true
Extracting Substrings:
substring(startIndex, endIndex) : Similar to slice , but handles negative indices differently (treats them as 0).
substr(startIndex, length) : Extracts a specified number of characters from a starting index (deprecated, prefer
slice or substring ).
Replacing Content:
Trimming Whitespace:
Concatenation:
Javascript Notes 11
console.log(combined); // Output: "Hello World"
4. Escape Characters
To include special characters or characters that have a special meaning within strings, you use a backslash ( \ ) as
an escape character:
\\ : Backslash
\n : Newline
\t : Tab
\r : Carriage return
console.log(path);
console.log(quote);
console.log(multiLine);
5. String Coercion
JavaScript often automatically converts values to strings when necessary (type coercion). This happens in
situations like:
Concatenation with a string: When you use the + operator with a string and another data type, the other data
type is converted to a string.
Using methods like alert() or console.log() : Values are implicitly converted to strings for display.
C. Boolean
In JavaScript, the Boolean data type is one of the most fundamental and represents a logical entity that can have
only one of two values: true or false . It's essential for controlling program flow, making decisions, and evaluating
conditions.
1. Boolean Values
The two boolean values are:
2. Creating Booleans
You typically don't explicitly create Boolean objects using new Boolean() . Instead, you assign the literal values true or
false directly.
Javascript Notes 12
let isLoggedIn = true;
There are a specific set of values that, when coerced to a boolean, become false. These are known as falsy
values:
0 (negative zero)
null
undefined
NaN (Not-a-Number)
Truthy Values:
Any value that is not one of the falsy values listed above is considered truthy and will coerce to true.
Functions
Infinity , Infinity
// Falsy examples
if (0) {
console.log("This will not execute.");
}
if ("") {
console.log("This will not execute.");
}
if (null) {
console.log("This will not execute.");
}
if (undefined) {
console.log("This will not execute.");
}
if (NaN) {
console.log("This will not execute.");
}
// Truthy examples
if (1) {
console.log("This will execute (1 is truthy).");
Javascript Notes 13
}
if ("hello") {
console.log("This will execute ('hello' is truthy).");
}
if ([]) {
console.log("This will execute (empty array is truthy).");
}
if ({}) {
console.log("This will execute (empty object is truthy).");
}
Boolean()
console.log(Boolean(0)); // false
console.log(Boolean("hello")); // true
Double NOT operator ( !! ): This is a common and concise idiom for converting a value to its boolean
equivalent.
console.log(!!0); // false
console.log(!!"hello"); // true
console.log(!![]); // true
Comparison Operators:
== (loose equality)
!= (loose inequality)
Logical Operators:
&& (Logical AND): Returns true if both operands are truthy, otherwise returns the first falsy operand or the
last operand.
|| (Logical OR): Returns true if at least one operand is truthy, otherwise returns the first truthy operand or
the last operand.
let a = true;
let b = false;
Javascript Notes 14
6. Usage of Booleans
Booleans are fundamental for:
if (isLoggedIn) {
console.log("Welcome back!");
} else {
console.log("Please log in.");
}
Loops: Determining when for and while loops should continue or terminate.
let count = 0;
while (count < 5) {
console.log(count);
count++;
}
Function Return Values: Functions often return booleans to indicate success or failure.
function isValidEmail(email) {
// (simple check)
return email.includes('@') && email.includes('.');
}
console.log(isValidEmail("test@example.com")); // true
console.log(isValidEmail("invalid-email")); // false
D. undefined
In JavaScript, undefined is a primitive value that represents the absence of a meaningful value. It's a fundamental
concept to grasp because it indicates that something has not yet been defined or assigned.
When you declare a variable using let or var (and historically const but it must be initialized), but you don't
assign any value to it, its default value is undefined.
let myVariable;
console.log(myVariable); // Output: undefined
var anotherVariable;
console.log(anotherVariable); // Output: undefined
Javascript Notes 15
function greet(name) {
console.log(`Hello, ${name}!`);
}
const person = {
name: "Bob",
age: 30
};
function doSomething() {
// No return statement
}
let result = doSomething();
console.log(result); // Output: undefined
function doAnotherThing() {
return; // Returns undefined explicitly
}
let anotherResult = doAnotherThing();
console.log(anotherResult); // Output: undefined
5. void Operator:
The void operator evaluates an expression and then returns undefined. It's often used in conjunction with
immediately invoked function expressions (IIFEs) or to prevent a function from returning a value.
undefined : Indicates that a variable has been declared but has not yet been assigned a value. It's the default
"empty" state. It's often a sign that something is missing or hasn't been initialized.
null: Represents the intentional absence of any object value. It's a value explicitly assigned by a programmer
to signify "no value," "empty," or "nothing."
Key Differences:
Javascript Notes 16
Equality undefined == null is true null == undefined is true
undefined === null is false (strict) null === undefined is false (strict)
Example:
1. Strict Equality ( === ): This is the recommended and most reliable way, as it avoids type coercion.
let value;
if (value === undefined) {
console.log("Value is undefined.");
}
2. typeof Operator: Useful when you're not sure if the variable itself has been declared (e.g., when dealing with
global variables or dynamic property access).
let x;
if (typeof x === 'undefined') {
console.log("x is undefined.");
}
3. Loose Equality ( == ): While it works, it also checks for null due to type coercion, which might not always be
the desired behavior.
let y = null;
if (y == undefined) { // This will be true because null == undefined is true
console.log("y is undefined (or null).");
}
Understanding undefined is crucial for debugging and writing robust JavaScript code, as it helps you identify
uninitialized variables or missing data.
E. Null
In JavaScript, null is a primitive value that represents the intentional absence of any object value. It's a
placeholder for "no value," "nothing," or "empty," specifically when referring to the absence of an object.
2. Explicit Assignment: Unlike undefined (which is often a default or implicit value), null is almost always explicitly
assigned by a programmer to indicate that a variable or property currently holds no meaningful value.
Javascript Notes 17
3. Intentional Absence: It signifies that a variable, which could conceptually hold an object, is currently pointing
to nothing.
4. typeof null is "object": This is a long-standing, historical bug or quirk in JavaScript. Despite null being a
primitive value, typeof null returns "object" . This is a known issue from the very first version of JavaScript and
cannot be fixed without breaking a lot of existing code on the web.
Resetting Variables: To clear the value of an existing object variable, making it explicitly empty. This can also
help with garbage collection if the object was large and no other references exist.
Function Return Values: Functions might return null to indicate that an expected object or value could not be
found or created.
function findUserById(id) {
if (id === 123) {
return { name: "Alice" };
} else {
return null; // User not found
}
}
DOM Manipulation (Old Browsers/Practices): In older browser environments, when a DOM element was not
found, methods like document.getElementById() might return null . Modern methods like querySelector often return null if
no element matches.
null : Explicitly assigned by a developer to mean "no value" or "empty." It's an intentional state.
: Implicitly assigned (by JavaScript engine) when a variable is declared but not initialized, a function
undefined
parameter is missing, an object property doesn't exist, or a function returns nothing. It means "value not yet
defined."
Comparison Table:
Javascript Notes 18
Intentional absence of object value (programmer
Meaning Value not assigned/defined (default)
assigned)
null === undefined is false (strict) undefined === null is false (strict)
2. Loose Equality ( == ): This will also be true if the value is undefined due to type coercion. Use with caution if you
need to differentiate between null and undefined .
3. Falsy Check (using ! or Boolean() ): Since null is a falsy value, it will evaluate to false in a boolean context.
F. Symbol
In JavaScript, a Symbol is a primitive data type introduced in ES6 (ECMAScript 2015). It represents a unique
and immutable value, primarily used to create unique identifiers for object properties, thereby avoiding name
collisions.
Before Symbols, if you wanted to add a property to an object that wouldn't accidentally clash with existing or
future properties (especially when dealing with third-party code or libraries), it was challenging. Symbols
solve this problem.
3. Primitive Data Type: Like number , string , boolean , null , undefined , and bigint , Symbol is a primitive. Its typeof
result is "symbol" .
4. Not Enumerable in for...in Loops: Properties whose keys are Symbols are not enumerable when using
for...in loops or Object.keys() . This makes them ideal for "hidden" or non-public properties within objects.
Javascript Notes 19
const myObject = {
name: "Alice",
[Symbol('secret')]: "Confidential Info"
};
Creating Symbols:
You create a Symbol by calling the Symbol() constructor (note: you do not use new Symbol() , as Symbols are not
constructible in the same way as regular objects).
: You can provide an optional string description (or "name") for a Symbol. This description is
Symbol(description)
purely for debugging purposes and does not affect the Symbol's uniqueness.
class User {
constructor(name, data) {
this.name = name;
this[PRIVATE_DATA] = data; // Using a Symbol as a property key
this[STATUS_MESSAGE] = "Online";
}
getPrivateData() {
return this[PRIVATE_DATA];
}
Javascript Notes 20
console.log(key); // Only 'name' will be logged
}
Object.getOwnPropertySymbols(obj) : Returns an array of all Symbol properties found directly on a given object.
Reflect.ownKeys(obj) : Returns an array containing both string and Symbol properties of an object.
Symbol.for(key) :JavaScript
Looks for a Symbol with the given key in the global Symbol registry.
If not found, it creates a new Symbol with that key , adds it to the registry, and then returns it.
This ensures that Symbol.for('foo') will always return the same Symbol instance across your application.
Symbol.keyFor(symbol) :
If the Symbol was created using Symbol.for() , it returns the string key (description) associated with it in
the global registry.
Symbol.iterator : Used to define the default iterator for an object (e.g., for for...of loops).
Javascript Notes 21
Symbol.toStringTag : Used to define the default string description of an object (e.g., Object.prototype.toString() ).
These well-known Symbols allow developers to customize the behavior of their objects in ways that were
previously not possible or required hacks.
Preventing name collisions: When merging objects from different sources, Symbols can be used to add
properties without worrying about existing property names.
Customizing built-in behaviors: Using well-known Symbols to alter how objects interact with core
language features.
Defining unique constants: While const is for constant values, Symbol() ensures uniqueness for identifiers.
G. BigInt
In JavaScript, the BigInt primitive data type was introduced in ES2020 (ECMAScript 2020) to address a
fundamental limitation of the standard Number type: its inability to accurately represent very large integers.
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 (!!! Precision loss - it's the same as
console.log(Number.MAX_SAFE_INTEGER + 3); // 9007199254740994 (!!! Another wrong result)
This loss of precision is unacceptable for applications requiring exact integer arithmetic for very large
numbers, such as:
Cryptographic operations
What is BigInt ?
BigInt is a primitive data type that can represent integers with arbitrary precision. This means it can handle
integers of any size, limited only by the available memory of your system.
1. Append n to an integer literal: This is the most common and straightforward way.
Javascript Notes 22
const bigNumber = 123456789012345678901234567890123456789n; // Notice the 'n' at the end
console.log(typeof bigNumber); // Output: "bigint"
2. Use the BigInt() constructor function: You can pass a Number or a String representing an integer to the
BigInt() constructor.
const a = 10n;
const b = 5n;
Comparison Operators: BigInt s can be compared with other BigInt s or even Number s using comparison
operators ( < , > , <= , >= , == , === , != , !== ).
Bitwise Operators: Bitwise operators ( & , | , ^ , ~ , << , >> , >>> ) also work with BigInt s.
// console.log(num + big); // TypeError: Cannot mix BigInt and other types, use explicit conversions
// Explicit conversions:
console.log(num + Number(big)); // Converts big to Number: 15
console.log(BigInt(num) + big); // Converts num to BigInt: 15n
Note: Comparison operators ( == , === ) are an exception where mixing is allowed for comparison, but
strict equality ( === ) will still return false if types differ.
2. Division Truncates Decimals: When dividing BigInt s, any fractional part is truncated (removed), similar to
integer division in other languages. It does not round.
Javascript Notes 23
3. JSON Serialization: BigInt values cannot be directly serialized to JSON using JSON.stringify() . This is because
JSON doesn't have a BigInt type. You'll get a TypeError .
const myData = {
id: 123n, // This will cause an error
name: "Test"
};
4. Boolean Coercion: BigInt values behave like Number s when coerced to Booleans: 0n is falsy, and all other
BigInt s are truthy.
if (0n) {
console.log("0n is falsy - won't print");
}
if (1n) {
console.log("1n is truthy - will print");
}
For unique identifiers that might exceed the safe integer range (e.g., database IDs that are very large
numbers).
In cryptographic algorithms or other areas where exact arithmetic with large integers is non-negotiable.
For most day-to-day numerical operations, the standard Number type remains perfectly adequate and is
generally more performant. Only reach for BigInt when you explicitly hit the integer precision limits of Number .
5. Operators in Javascript
In JavaScript, operators are special symbols or keywords that perform operations on one or more values
(called operands) and return a result. They are the backbone of any computation, decision-making, and data
manipulation within your code.
JavaScript has a rich set of operators, which can be broadly categorized as follows:
1. Assignment Operators
Used to assign values to variables.
= (Assignment): Assigns the value of the right operand to the left operand.
let x = 10;
Javascript Notes 24
= (Subtraction assignment): x=x-y
|= (Bitwise OR assignment)
let a = 5;
a += 3; // a is now 8 (5 + 3)
let b = 10;
b /= 2; // b is now 5 (10 / 2)
2. Arithmetic Operators
Perform mathematical calculations.
+ (Addition)
(Subtraction)
(Multiplication)
/ (Division)
Prefix: -x
Postfix: x--
let i = 0;
console.log(++i); // 1 (i becomes 1, then 1 is logged)
let j = 0;
console.log(j++); // 0 (0 is logged, then j becomes 1)
3. Comparison Operators
Javascript Notes 25
Compare two values and return a boolean ( true or false ).
== (Loose Equality): Checks if two operands are equal after type coercion (converting types if they are
different).
=== (Strict Equality): Checks if two operands are equal without type coercion (both value and type must
be the same). Generally preferred for predictability.
!= (Loose Inequality): Checks if two operands are not equal after type coercion.
!== (Strict Inequality): Checks if two operands are not equal without type coercion. Generally preferred.
4. Logical Operators
Combine boolean (or truthy/falsy) values and return a boolean (or the value of one of the operands).
&& (Logical AND): Returns true if both operands are truthy. Otherwise, it returns the first falsy operand
encountered, or the last operand if all are truthy.
(Logical OR): Returns true if at least one operand is truthy. Otherwise, it returns the first truthy operand
||
5. Bitwise Operators
Perform operations on the binary representation of numbers. These are less commonly used in typical web
development but are important for specific tasks like low-level manipulations or optimizing certain algorithms.
| (Bitwise OR)
^ (Bitwise XOR)
~ (Bitwise NOT)
6. String Operators
Primarily used for string concatenation.
Javascript Notes 26
+ (Concatenation): Used to join two or more strings.
8. Unary Operators
Operate on a single operand.
! (Logical NOT)
console.log(+"5"); // 5 (number)
console.log(-"10"); // -10 (number)
console.log(typeof "hello"); // "string"
class Car {}
let myCar = new Car();
console.log(myCar instanceof Car); // true
console.log([] instanceof Array); // true
Spread Syntax: Expands an iterable (like an array or string) into individual elements.
Javascript Notes 27
Rest Parameters: Gathers an indefinite number of arguments into an array.
function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
6. Conditional Statement
A conditional statement in JavaScript allows you to execute different blocks of code based on whether a certain
condition is true or false. This is fundamental for creating dynamic and interactive web applications. Here are the
primary conditional statements in JavaScript:
1. if statement
The if statement is the most basic conditional statement. It executes a block of code if a specified condition is
true.
Syntax:
if (condition) {
// code to be executed if the condition is true
}
Example:
2. if...else statement
The if...else statement executes one block of code if the condition is true and another block if the condition is false.
Syntax:
Javascript Notes 28
if (condition) {
// code to be executed if the condition is true
} else {
// code to be executed if the condition is false
}
Example:
if (condition1) {
// code to be executed if condition1 is true
} else if (condition2) {
// code to be executed if condition2 is true
} else {
// code to be executed if none of the above conditions are true
}
Example:
4. switch statement
The switch statement is used to perform different actions based on different conditions. It is often a more elegant
alternative to a long if...else if...else chain when you are testing a single variable against multiple possible values.
Syntax:
switch (expression) {
case value1:
// code to be executed if expression matches value1
break;
case value2:
// code to be executed if expression matches value2
Javascript Notes 29
break;
default:
// code to be executed if expression doesn't match any case
}
Important Notes:
The break keyword is crucial. It stops the execution of code inside the switch block once a match is found.
Without break , the execution would "fall through" to the next case .
The default keyword is optional and specifies the code to run if there is no case match.
Example:
switch (day) {
case "Monday":
console.log("Start of the week!");
break;
case "Wednesday":
console.log("Mid-week!");
break;
case "Friday":
console.log("Almost the weekend!");
break;
default:
console.log("Another day.");
}
// Output: Mid-week!
Example:
switch : Use when you have a single expression that you want to compare against multiple possible values. It
often makes the code cleaner than a long if...else if chain.
Ternary Operator: Use for very concise if...else statements where you need to assign a value based on a
condition. It's great for inline conditions.
Javascript Notes 30
7. Loops in Javascript
Loops in JavaScript are fundamental control flow statements that allow you to repeatedly execute a block of code.
They are essential for tasks that involve processing lists of data, repeating actions a specific number of times, or
continuing an operation until a certain condition is met.
Here's a breakdown of the main types of loops in JavaScript:
1. for loop:
Purpose: The most common loop, used when you know the exact number of iterations beforehand.
Syntax:
Explanation:
condition : Evaluated before each iteration. If true , the loop body executes. If false , the loop terminates.
Example:
2. while loop:
Purpose: Executes a block of code as long as a specified condition is true . The condition is checked
before each iteration.
Syntax:
while (condition) {
// code to be executed
}
Example:
let count = 0;
while (count < 3) {
console.log("Count: " + count);
count++;
}
// Output:
// Count: 0
// Count: 1
// Count: 2
3. do...while loop:
Purpose: Similar to while , but it guarantees that the loop body executes at least once before the condition
is checked.
Syntax:
Javascript Notes 31
do {
// code to be executed
} while (condition);
Example:
let i = 0;
do {
console.log("Iteration: " + i);
i++;
} while (i < 0); // Condition is false, but it runs once
// Output:
// Iteration: 0
4. for...in loop:
Syntax:
Example:
const person = {
name: "Alice",
age: 30,
city: "New York"
};
Important Note: While for...in can iterate over array indices, it's generally not recommended for arrays
because it can also iterate over inherited properties and the order of iteration is not guaranteed. Use for or
for...of for arrays.
5. for...of loop:
Purpose: Iterates over the values of iterable objects (like Arrays, Strings, Maps, Sets, NodeLists, etc.).
This is the preferred way to iterate over arrays and other iterable collections.
Syntax:
Example:
Javascript Notes 32
// apple
// banana
// cherry
continue : Skips the current iteration of the loop and proceeds to the next iteration.
while loop: When the number of iterations is unknown, and the loop continues as long as a condition is true.
Be careful to avoid infinite loops by ensuring the condition eventually becomes false.
do...while loop: When you need the loop body to execute at least once, regardless of the initial condition.
for...of loop: For iterating over the values of iterable collections like arrays, strings, maps, and sets. This is
generally the most modern and readable choice for iterating over collection elements.
for...in loop: Primarily for iterating over the keys (property names) of plain JavaScript objects. Use with caution
due to its behavior with prototype chains.
8. Functions
Functions in JavaScript are one of the most fundamental and powerful building blocks of the language. They are
blocks of reusable code that perform a specific task or calculate a value. Functions allow you to organize your
code, make it more modular, readable, and prevent code repetition (DRY - Don't Repeat Yourself).
Here's a comprehensive breakdown of functions in JavaScript:
1. Defining Functions
There are several ways to define functions in JavaScript:
Javascript Notes 33
a) Function Declarations (Function Statements)
This is the most common and traditional way to define a function.
function greet(name) {
return "Hello, " + name + "!";
}
Key characteristics:
They are "hoisted," meaning they can be called before they are defined in the code.
b) Function Expressions
Functions can also be defined as expressions and assigned to a variable.
Key characteristics:
They are not hoisted in the same way as function declarations. You cannot call sayHi before its definition.
They can be named or anonymous (as shown above). Named function expressions are useful for recursion or
debugging.
Key characteristics:
No function keyword.
Javascript Notes 34
No explicit return keyword needed for single-expression bodies (the expression is implicitly returned).
Lexical this binding (doesn't create its own this context, inherits from the surrounding scope). This is a
significant difference from regular functions.
Arguments: The actual values passed to the function when it's called (e.g., "Alice" in greet("Alice") ).
function sumAll(...numbers) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
3. Returning Values
Functions can return a value using the return statement. If no return statement is specified, the function implicitly
returns undefined .
function doNothing() {
// No return statement
}
console.log(doNothing()); // Output: undefined
4. Scope
Functions create their own scope. Variables declared inside a function are local to that function and cannot be
accessed from outside.
Javascript Notes 35
let globalVar = "I'm global";
function myFunction() {
let localVar = "I'm local";
console.log(globalVar); // Accessible
console.log(localVar); // Accessible
}
myFunction();
// console.log(localVar); // Error: localVar is not defined
5. Higher-Order Functions
Functions in JavaScript are "first-class citizens," meaning they can be:
Assigned to variables.
Functions that take other functions as arguments or return functions as results are called higher-order functions.
console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15
(function() {
let privateVariable = "This is private.";
console.log(privateVariable); // Output: This is private.
})();
Javascript Notes 36
Inside a regular function (not arrow functions), the arguments object is an array-like object that contains all the
arguments passed to the function.
function showArguments() {
console.log(arguments);
console.log(arguments[0]);
}
It's generally more common and recommended to use rest parameters ( ...args ) for modern JavaScript as they
provide a real array and are more flexible.
Summary
Functions are indispensable in JavaScript for:
9. Objects
In JavaScript, an object is a fundamental, non-primitive data type that allows you to store collections of related
data and more complex entities. It's essentially a collection of key-value pairs, where each key (also called a
property name) is a string (or a Symbol, in modern JavaScript) that uniquely identifies a value. The value can be
any JavaScript data type, including other objects, functions, numbers, strings, booleans, arrays, etc.
Think of an object like a real-world object (e.g., a car, a person, a book). A car has properties like "color," "make,"
"model," "year," and it might have actions it can perform, like "start" or "stop." In JavaScript, these properties are
the key-value pairs, and the actions are represented by functions stored as properties (which are then called
methods).
const person = {
name: "Alice", // name is a key, "Alice" is its value (string)
age: 30, // age is a key, 30 is its value (number)
isStudent: false // isStudent is a key, false is its value (boolean)
};
2. Methods: When a property's value is a function, it's called a method. Methods define behaviors or actions
that an object can perform.
const dog = {
name: "Buddy",
breed: "Golden Retriever",
bark: function() { // bark is a method
console.log("Woof woof!");
},
greet: function() {
console.log(`Hello, my name is ${this.name}.`); // 'this' refers to the current object
}
};
Javascript Notes 37
dog.bark(); // Output: Woof woof!
dog.greet(); // Output: Hello, my name is Buddy.
The this keyword inside a method refers to the object that the method belongs to.
3. Dynamic Nature: Objects are dynamic, meaning you can add, modify, or delete properties and methods at
runtime after the object has been created.
const car = {
make: "Toyota",
model: "Camry"
};
// Add a method
car.start = function() {
console.log("Engine started!");
};
car.start(); // Output: Engine started!
// Delete a property
delete car.year;
console.log(car); // { make: "Toyota", model: "Corolla", start: [Function: start] }
4. Reference Type: Objects in JavaScript are reference types. When you assign an object to a variable, the
variable doesn't store the object itself, but rather a reference (a pointer) to where the object is stored in
memory.
const obj1 = { a: 1 };
const obj2 = obj1; // obj2 now refers to the same object as obj1
obj2.a = 2;
console.log(obj1.a); // Output: 2 (because both variables point to the same object)
const obj3 = { a: 1 };
const obj4 = { a: 1 };
console.log(obj3 === obj4); // Output: false (they are different objects in memory, even if they have the same
const user = {
firstName: "John",
lastName: "Doe",
age: 25,
email: "john.doe@example.com"
};
Javascript Notes 38
You can use the Object constructor.
3. Constructor Functions:
Used when you need to create multiple objects of the same "type" or "blueprint."
class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
}
makeSound() {
console.log(`${this.name} makes a sound.`);
}
}
5. Object.create():
Creates a new object using an existing object as the prototype of the newly created object.
const animalPrototype = {
makeSound: function() {
console.log(`${this.name} makes a sound.`);
}
};
Javascript Notes 39
console.log(user.firstName); // Output: John
console.log(user.age); // Output: 25
2. Bracket Notation (useful for dynamic property names or keys with special characters/spaces):
Object.keys() : Returns an array of a given object's own enumerable property names (keys).
Object.entries() : Returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
Javascript Notes 40
Object.assign(target, ...sources) : Copies all enumerable own properties from one or more source objects to a target
object.
const objA = { a: 1, b: 2 };
const objB = { c: 3 };
const mergedObj = Object.assign({}, objA, objB);
console.log(mergedObj); // { a: 1, b: 2, c: 3 }
(Note: The spread syntax { ...objA, ...objB } is often preferred for merging objects in modern JS as it's more
concise.)
Object.freeze(obj) : Freezes an object. Other code cannot delete or add properties to the object, and cannot
change the values of existing properties.
Object.seal(obj) : Seals an object. Other code can change the values of existing properties but cannot delete or
add properties.
Object.hasOwnProperty(prop) : Returns a boolean indicating whether the object has the specified property as its own
property (not inherited).
10. Array
In JavaScript, an array is a special type of object that is used to store ordered collections of data. Unlike regular
objects where data is accessed by named keys, array elements are accessed by their numerical index, starting
from 0 .
Arrays are incredibly versatile and are fundamental for handling lists of items, such as a list of names, numbers, or
even other objects.
Here's a detailed breakdown of arrays in JavaScript:
3. Heterogeneous: An array can store elements of different data types (numbers, strings, booleans, objects,
other arrays, functions, etc.) within the same array.
4. Dynamic Size: Arrays can grow or shrink in size as needed; you don't need to pre-define their capacity.
5. Object Type: Arrays are technically objects in JavaScript, inheriting properties and methods from Array.prototype .
typeof [] will return "object" . To check if a variable is an array, use Array.isArray() .
Creating Arrays:
There are several ways to create arrays:
2. Array() Constructor:
Less common for simple arrays, but useful for creating an array of a specific size with empty slots.
Javascript Notes 41
Caution: If you pass a single number to new Array() , it creates an array of that length filled with empty slots, not
an array with that number as its single element.
Adding/Removing Elements:
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.
unshift() : Adds one or more elements to the beginning of an array and returns the new length.
Javascript Notes 42
const arr = [3, 4];
arr.unshift(1, 2);
console.log(arr); // [1, 2, 3, 4]
shift() : Removes the first element from an array and returns that element.
splice(startIndex, deleteCount, ...itemsToAdd) : A powerful method for adding, removing, or replacing elements at any
position.
Searching/Finding Elements:
indexOf(element, fromIndex) : Returns the first index at which a given element can be found, or -1 if it is not present.
lastIndexOf(element, fromIndex) : Returns the last index at which a given element can be found, or -1 if it is not
present.
includes(element, fromIndex) (ES6+): Returns true if an array contains a specified element, false otherwise.
find(callback) (ES6+): Returns the value of the first element in the array that satisfies the provided testing
function.
findIndex(callback) (ES6+): Returns the index of the first element in the array that satisfies the provided testing
function.
map(callback) : Creates a new array with the results of calling a provided function on every element in the calling
array.
filter(callback) : Creates a new array with all elements that pass the test implemented by the provided function.
Javascript Notes 43
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]
reduce(callback, initialValue) : Executes a reducer function on each element of the array, resulting in a single output
value.
Transforming/Combining Arrays:
slice(startIndex, endIndex) : Returns a shallow copy of a portion of an array into a new array. Original array is not
modified.
concat(...arrays) : Used to merge two or more arrays. This method does not change the existing arrays, but
instead returns a new array.
(Note: The spread syntax [...arr1, ...arr2] is often preferred for combining arrays in modern JS.)
sort(compareFunction) : Sorts the elements of an array in place. For numbers, you often need a compareFunction .
Javascript Notes 44
// banana
// cherry
The DOM organizes all the elements of an HTML document in a hierarchical tree structure.
Each element, attribute, and even text content within your HTML is represented as a node in this tree.
Example HTML:
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1 id="main-heading">Welcome!</h1>
<p class="intro">This is a paragraph.</p>
<button>Click me</button>
</body>
</html>
Document
└── html
├── head
│ └── title
│ └── "My Page" (Text Node)
└── body
├── h1 (id="main-heading")
│ └── "Welcome!" (Text Node)
├── p (class="intro")
│ └── "This is a paragraph." (Text Node)
└── button
└── "Click me" (Text Node)
2. Nodes:
Everything in the DOM is a node. Common node types include:
Javascript Notes 45
Element Node: Represents an HTML tag (e.g., <h1> , <p> , <div> ).
Attribute Node: Represents attributes of an element (e.g., id="main-heading" , class="intro" ). Though often
accessed as properties of element nodes, they are distinct nodes in the full DOM spec.
Change Attributes: Modify attribute values (e.g., src of an image, href of a link).
Add/Remove Elements: Dynamically create new HTML elements and append them to the page, or remove
existing ones.
Handle Events: Respond to user interactions (clicks, key presses, mouse movements, form submissions,
etc.).
1. Selecting Elements:
document.getElementById('idName') : Selects a single element by its unique id attribute. (Most efficient for single
elements.)
document.querySelector('CSS-selector') (ES5+): Returns the first element that matches the specified CSS selector.
document.querySelectorAll('CSS-selector') (ES5+): Returns a NodeList (static, array-like object) of all elements that match
the specified CSS selector.
2. Modifying Content:
element.textContent : Gets or sets the text content of an element (plain text only, ignores HTML tags within).
Javascript Notes 46
heading.textContent = "Hello, DOM!";
// Now the h1 displays "Hello, DOM!"
: Gets or sets the HTML content (including tags) of an element. Use with caution to prevent
element.innerHTML
3. Modifying Attributes:
element.attributeName : Direct property access for common attributes.
4. Modifying Styles:
element.style.propertyName : Directly sets inline CSS styles.
heading.style.color = "blue";
heading.style.fontSize = "40px";
For multiple styles or more complex styling, it's often better to toggle CSS classes.
Javascript Notes 47
newParagraph.appendChild(textNode);
document.body.appendChild(newParagraph); // Adds it to the end of the body
6. Removing Elements:
element.remove() (Modern JS): Removes the element itself.
7. Event Handling:
The DOM allows you to attach event listeners to elements to react to user interactions.
element.onclick = function() { ... } (Older/Simpler): Direct event handler property. Limited to one handler per event
type.
Fetch data from servers and update parts of the page without a full reload (AJAX).
if (true) {
let x = 10;
console.log(x); // 10
}
// console.log(x); // ReferenceError: x is not defined (x is block-scoped)
let count = 0;
count = 1; // Allowed
Javascript Notes 48
const : Declares a block-scoped constant. Once assigned, its value (or reference for objects/arrays) cannot be
reassigned.
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable.
Benefit: Reduces bugs related to variable hoisting and unintended reassignments, leading to more robust code.
Key Difference ( this binding): Arrow functions do not have their own this context. They lexically inherit this from
their enclosing scope. This solves a common pain point in older JavaScript with callback functions and this
context.
// Before ES6
const messageOld = "My name is " + name + " and I am " + age + " years old.";
// Multi-line strings
const multiLineText = `
This is a string
that spans
multiple lines.
Javascript Notes 49
`;
console.log(multiLineText);
Benefit: Much more readable and easier string interpolation, especially for complex strings.
4. Destructuring Assignment
Allows you to unpack values from arrays or properties from objects into distinct variables in a concise way.
Array Destructuring:
// Skip elements
const [, , lastColor] = colors;
console.log(lastColor); // "blue"
Object Destructuring:
// Default values
const { city = "Unknown" } = person;
console.log(city); // "Unknown"
Benefit: Cleaner code for extracting data from complex objects and arrays.
Spread Operator ( ... ): Expands an iterable (like an array or string) or an object into individual elements.
Array concatenation/copying:
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const mergedObj = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }
const copiedObj = { ...obj1 }; // Creates a shallow copy
Javascript Notes 50
Rest Parameter ( ... ): Gathers an indefinite number of arguments into an array.
Benefit: Simplifies array/object manipulation, function arguments, and makes code more readable.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() { // Method
return `Hello, my name is ${this.name}.`;
}
study() {
console.log(`${this.name} is studying.`);
}
}
Benefit: Provides a clearer, more structured way to create objects and implement inheritance, aligning with class-
based languages.
Javascript Notes 51
export const PI = 3.14159;
export default function multiply(a, b) { // Only one default export per module
return a * b;
}
app.js :
console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
console.log(multiplyNumbers(4, 5)); // 20
Benefit: Solves the problem of global scope pollution, improves code maintainability, and enables better tooling
for bundling and optimization.
8. Default Parameters
Allows you to specify default values for function parameters. If an argument is not provided (or is undefined ), the
default value is used.
Benefit: Makes functions more robust and reduces the need for manual checks for missing arguments.
function fetchData() {
return new Promise((resolve, reject) => {
// Simulate an asynchronous operation (e.g., network request)
setTimeout(() => {
const success = true; // Change to false to see the catch block
if (success) {
resolve("Data fetched successfully!");
} else {
reject("Failed to fetch data.");
}
}, 2000);
});
}
fetchData()
.then(data => {
console.log(data); // "Data fetched successfully!"
})
Javascript Notes 52
.catch(error => {
console.error(error); // "Failed to fetch data."
})
.finally(() => {
console.log("Operation complete.");
});
getData();
Benefit: Makes asynchronous code even easier to read, write, and debug than raw Promises.
Map and Set : New built-in data structures for key-value pairs (Map) and unique values (Set).
Rest/Spread properties (for objects - ES2018): Similar to arrays, but for objects.
Optional Chaining ( ?. ) (ES2020): Safely access properties of deeply nested objects without fear of errors if
an intermediate property is null or undefined .
Nullish Coalescing Operator ( ?? ) (ES2020): Provides a default value only when the left-hand side is null or
undefined (unlike || which also treats 0 , '' , false as falsy).
console.log("Start");
Javascript Notes 53
// This function simulates a long-running synchronous task
function heavyCalculation() {
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += i;
}
return sum;
}
console.log("End");
In a browser, running heavyCalculation() would make the page unresponsive until it finishes.
Database operations.
Even though JavaScript is single-threaded (it has one main call stack that executes code), it achieves
asynchronicity by offloading time-consuming tasks to web APIs (in browsers) or Node.js APIs (in Node.js). When
these external operations complete, they put a callback function onto the callback queue (also known as the
message queue or task queue). The event loop then continuously checks the call stack and, if it's empty, it takes
functions from the callback queue and pushes them onto the call stack for execution.
1. Callbacks (Historical/Foundation):
A callback is a function passed as an argument to another function, which is then executed after the main
function completes its task.
console.log("Start");
function fetchData(callback) {
setTimeout(() => { // Simulate network request
const data = "Data from server";
callback(data); // Call the provided function with the data
}, 2000); // Wait for 2 seconds
}
fetchData(function(result) {
console.log(result); // This runs after 2 seconds
});
console.log("End");
// Output:
// Start
Javascript Notes 54
// End
// (after 2 seconds) Data from server
Problem: Callback Hell (Pyramid of Doom): When you have multiple nested asynchronous operations,
callbacks can lead to deeply indented, unreadable, and hard-to-maintain code.
console.log("Start");
function fetchDataPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // Simulate success/failure
if (success) {
resolve("Data from server (Promise)"); // Operation successful
} else {
reject("Error: Failed to fetch data"); // Operation failed
}
}, 2000);
});
}
fetchDataPromise()
.then(data => { // Handles successful resolution
console.log(data);
return "Processed: " + data; // You can chain .then()
})
.then(processedData => {
console.log(processedData);
})
.catch(error => { // Handles rejections/errors anywhere in the chain
console.error(error);
})
.finally(() => { // Always executes, regardless of success or failure
console.log("Promise operation complete.");
});
console.log("End");
// Output:
// Start
// End
Javascript Notes 55
// (after 2 seconds) Data from server (Promise)
// (after 2 seconds) Processed: Data from server (Promise)
// (after 2 seconds) Promise operation complete.
Promises significantly improve readability and error handling compared to nested callbacks.
3. Async/Await (ES2017):
Built on top of Promises, async/await provides syntactic sugar that makes asynchronous code look and
behave more like synchronous code, making it even easier to read and write.
The await keyword can only be used inside an async function. It pauses the execution of the async function
until the Promise it's waiting for settles (resolves or rejects).
console.log("Start");
function fetchDataAsync() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Data from server (Async/Await)");
}, 2000);
});
}
const processed = await new Promise(res => setTimeout(() => res("Processed async data"), 1000));
console.log(processed);
} catch (error) {
console.error("An error occurred:", error);
} finally {
console.log("Async operation finished.");
}
}
processData();
console.log("End");
// Output:
// Start
// End
// Fetching data...
// (after 2 seconds) Data from server (Async/Await)
// (after another 1 second) Processed async data
// (immediately after previous) Async operation finished.
async/ await is the preferred way to handle asynchronous code in modern JavaScript due to its exceptional
readability and synchronous-like error handling ( try...catch ).
Performance: Allows the application to perform multiple operations seemingly "at the same time," utilizing
network latency or I/O waits effectively.
Javascript Notes 56
Efficiency: Prevents the main thread from being tied up, enabling more efficient use of system resources.
14. Oops in Js
Object-Oriented Programming (OOP) in JavaScript
JavaScript is a multi-paradigm language, meaning it supports various programming styles, including functional,
imperative, and object-oriented. While it doesn't have traditional class-based inheritance like Java or C++, it
achieves OOP principles through prototypal inheritance.
Core Concepts of OOP:
Regardless of the language, OOP revolves around several key concepts:
1. Objects: The fundamental building blocks. In JavaScript, almost everything is an object (arrays, functions,
even primitive wrappers). Objects are collections of properties (data) and methods (functions that operate on
that data).
const car = {
make: "Toyota",
model: "Camry",
year: 2023,
start: function() { // This is a method
console.log("Engine started!");
},
honk() { // Shorthand method syntax (ES6+)
console.log("Beep beep!");
}
};
2. Encapsulation: The bundling of data (properties) and methods that operate on the data into a single unit (an
object). It also involves restricting direct access to some of an object's components, which is typically
achieved through private variables and methods.
In JavaScript, true private members were historically difficult to achieve, relying on closures. However, with
ES2022, private class fields ( # ) provide native support for true encapsulation within classes.
class BankAccount {
#balance; // Private field (ES2022+)
constructor(initialBalance) {
if (initialBalance < 0) {
throw new Error("Initial balance cannot be negative.");
}
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`Deposited ${amount}. New balance: ${this.#balance}`);
} else {
console.log("Deposit amount must be positive.");
}
}
withdraw(amount) {
if (amount > 0 && this.#balance >= amount) {
Javascript Notes 57
this.#balance -= amount;
console.log(`Withdrew ${amount}. New balance: ${this.#balance}`);
} else {
console.log("Invalid withdrawal amount or insufficient funds.");
}
}
getBalance() {
return this.#balance; // Accessible only from within the class
}
}
3. Inheritance: A mechanism where one object (the "child" or "subclass") acquires the properties and methods
of another object (the "parent" or "superclass"). This promotes code reusability.
JavaScript uses prototypal inheritance, where objects inherit directly from other objects. ES6 classes
provide a more familiar syntactic sugar over this prototypal mechanism.
eat() {
console.log(`${this.name} is eating.`);
}
}
bark() {
console.log("Woof woof!");
}
4. Polymorphism: The ability of objects of different classes to respond to the same method call in their own
specific way. This often involves method overriding, where a subclass provides its own implementation of a
method already defined in its superclass.
Javascript Notes 58
The eat() method in the Dog class above is an example of polymorphism. Both Animal and Dog have an eat()
function makeThemEat(creature) {
creature.eat(); // Calls the appropriate eat() method based on the object's type
}
5. Abstraction (Implicit in JS): Hiding complex implementation details and showing only the essential features of
an object. In JavaScript, this is often achieved implicitly through well-defined methods and private
fields/closures, rather than explicit abstract classes or interfaces (which JS doesn't have natively). You expose
a clean interface to interact with an object without needing to know its internal workings.
The BankAccount example demonstrates abstraction: you interact with deposit , withdraw , and getBalance without
needing to know exactly how #balance is managed internally.
console.log(personA.greet === personB.greet); // true (they share the same function from the prototype)
console.log(personA.__proto__ === OldPerson.prototype); // true
ES6 class syntax is essentially syntactic sugar over this prototypal inheritance model. It makes it much easier for
developers coming from class-based languages to write object-oriented code in JavaScript.
Modularity: Creates independent units of code that are easier to develop, test, and maintain.
Javascript Notes 59
Reusability: Inheritance and composition allow you to reuse code, reducing redundancy.
Maintainability: Changes in one part of the system are less likely to break other parts.
Error handling in JavaScript is crucial for creating robust, reliable, and user-friendly applications. It allows your
program to gracefully recover from unexpected situations, provide meaningful feedback to users, and prevent the
application from crashing.
1. try...catch...finally Statement
This is the primary way to handle synchronous errors in JavaScript.
catch block: Contains the code that is executed if an error occurs within the try block. It receives the error
object as an argument.
finally block (optional): Contains code that is executed regardless of whether an error occurred or not. This is
useful for cleanup operations (e.g., closing files, releasing resources).
Basic Example:
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero."); // Manually throw an error
}
return a / b;
}
try {
let result1 = divide(10, 2);
console.log("Result 1:", result1); // Output: Result 1: 5
function readFile(filePath) {
let fileHandle; // This would typically be a resource handle
try {
// Simulate opening a file
console.log(`Opening file: ${filePath}`);
fileHandle = "someFileResource";
Javascript Notes 60
console.log("File content read successfully.");
return "File content data";
} catch (error) {
console.error("Error in file operation:", error.message);
return null; // Indicate failure
} finally {
if (fileHandle) {
console.log(`Closing file handle for: ${filePath}`);
// Actual cleanup code would go here
fileHandle = null;
}
}
}
readFile("mydata.txt");
console.log("---");
readFile("another.txt"); // Might fail or succeed
name : The type of the error (e.g., 'Error' , 'TypeError' , 'ReferenceError' , 'RangeError' , 'SyntaxError' ).
: (Non-standard, but widely supported) A string representing the call stack at the moment the error was
stack
try {
console.log(undeclaredVar);
} catch (e) {
console.error(e.name + ": " + e.message); // ReferenceError: undeclaredVar is not defined
}
try {
null.f(); // Calling a method on null
} catch (e) {
console.error(e.name + ": " + e.message); // TypeError: Cannot read properties of null (reading 'f')
}
SyntaxError : Occurs when there's an invalid syntax in the code. These errors typically prevent the script from
even running.
// console.log("Hello; // SyntaxError: missing ) after argument list (this would prevent execution)
try {
const arr = new Array(-1);
} catch (e) {
console.error(e.name + ": " + e.message); // RangeError: Invalid array length
}
Javascript Notes 61
URIError : Error in global URI functions like encodeURI() .
EvalError : Errors related to the global eval() function (less common in modern JS).
function fetchUserData(userId) {
if (userId <= 0) {
throw new CustomAPIError("Invalid User ID provided.", 400);
}
// Simulate API call
if (userId === 123) {
throw new CustomAPIError("User not found.", 404);
}
return { id: userId, name: "User " + userId };
}
try {
let user = fetchUserData(-5);
console.log(user);
} catch (error) {
if (error instanceof CustomAPIError) {
console.error(`API Error (${error.statusCode}): ${error.message}`);
} else {
console.error("Unknown error:", error.message);
}
}
try {
let user = fetchUserData(123);
console.log(user);
} catch (error) {
if (error instanceof CustomAPIError) {
console.error(`API Error (${error.statusCode}): ${error.message}`); // API Error (404): User not found.
} else {
console.error("Unknown error:", error.message);
}
}
function fetchData() {
return new Promise((resolve, reject) => {
Javascript Notes 62
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve("Data fetched successfully!");
} else {
reject(new Error("Network error during data fetch."));
}
}, 1000);
});
}
fetchData()
.then(data => {
console.log(data);
// If an error happens here, the next .catch will still handle it
// throw new Error("Error during data processing");
})
.catch(error => { // Catches any rejection in the chain above it
console.error("Promise Error:", error.message);
})
.finally(() => {
console.log("Fetch operation complete.");
});
getData();
Javascript Notes 63
};
Node.js ( process.on('uncaughtException') ): Catches uncaught exceptions in Node.js processes. Use with extreme
caution as it indicates a critical state and the process should ideally be restarted after logging.
Catch Errors at Appropriate Levels: Don't wrap every single line in a try...catch . Catch errors where you can
meaningfully handle them or where they might prevent further execution.
Be Specific with catch : Use instanceof to differentiate between custom error types or specific built-in errors.
Provide User Feedback: If an error impacts the user, inform them appropriately (e.g., "Something went wrong,
please try again.").
Log Errors: Always log errors (to the console, a logging service, or a file) for debugging and monitoring.
Avoid Empty catch Blocks: Never have an empty catch block that swallows errors. This makes debugging
incredibly difficult.
Use Promises and async/await for Asynchronous Code: These patterns are much preferred over nested
callbacks for managing async errors.
Centralized Error Handling (for APIs): In backend APIs, implement a centralized error handling middleware or
function to send consistent error responses to clients.
Node.js: A JavaScript runtime environment that allows you to execute JavaScript code outside a web
browser (e.g., for backend servers, build tools, command-line utilities). It's the foundation for most
JavaScript tooling.
npm (Node Package Manager): The default package manager for Node.js. Used to install, manage, and
publish JavaScript packages (libraries, frameworks, tools).
Yarn/pnpm: Alternative package managers that offer potential benefits like faster installation, better
dependency management, or more efficient disk space usage.
Javascript Notes 64
Workflow: Initialize projects ( npm init ), install dependencies ( npm install <package> ), run scripts defined in
package.json .
VS Code (Visual Studio Code): The most popular choice, offering excellent JavaScript/TypeScript
support, a vast extension ecosystem (linters, formatters, debuggers, etc.), and integrated terminal.
WebStorm: A powerful commercial IDE from JetBrains, known for its deep understanding of JavaScript
ecosystems.
Sublime Text, Atom: Other popular text editors with good JavaScript support via plugins.
Workflow: Writing code, intelligent autocompletion, syntax highlighting, integrated debugging, Git
integration.
Purpose: Static code analysis tools that identify problematic patterns found in JavaScript code (potential
bugs, stylistic issues, bad practices) without executing the code.
ESLint: The de-facto standard. Highly configurable, allowing you to enforce coding standards, integrate
with frameworks (React, Vue), and apply best practices.
Workflow: Run eslint as part of your pre-commit hooks or build process; integrate into your IDE for real-
time feedback.
2. Formatters (Prettier):
Purpose: Automatically format your code to adhere to consistent style guidelines (indentation, spacing,
line breaks, semicolons).
Prettier: The most popular choice, opinionated but highly effective. It removes debates over code style.
Workflow: Integrate into your IDE (format on save), run as a pre-commit hook, or as part of a CI/CD
pipeline. Often used in conjunction with ESLint.
Purpose: Convert modern JavaScript (ES6+ features, TypeScript, JSX) into backward-compatible
JavaScript (ES5 or lower) that older browsers and environments can understand.
Babel: The primary tool for this. It uses plugins and presets to transform specific syntax features.
Workflow: Configure Babel in your project to run during the build process, typically integrated with a
module bundler.
Purpose: Take multiple JavaScript (and often CSS, images, etc.) files and combine them into a single (or a
few) optimized bundle files for deployment. They manage dependencies, handle optimizations
(minification, tree-shaking), and enable code splitting.
Webpack: Extremely powerful and highly configurable, the most widely used for complex applications.
Rollup: Optimized for JavaScript libraries and smaller applications, excels at "tree-shaking" (removing
unused code).
Vite: A newer, very fast build tool that leverages native ES modules in development, offering a significantly
faster dev server experience.
Workflow: Define entry points and output bundles in a configuration file; run the bundler from the
command line or as part of a build script.
Purpose: Automate repetitive development tasks (e.g., compiling Sass, minifying images, running tests).
Modern Approach: For most common tasks, npm scripts (scripts defined in package.json that run command-
line tools) or integrated features within bundlers (like Webpack's dev server) have largely replaced
Javascript Notes 65
dedicated task runners like Gulp or Grunt.
Workflow: Define scripts in package.json (e.g., "start": "webpack serve" , "build": "webpack" , "test": "jest" ), then run npm
run <script-name> .
IV. Testing
1. Testing Frameworks:
Jest: A popular, batteries-included testing framework from Facebook, often used for React applications
but suitable for any JavaScript project. Includes assertion library, mocking, and test runner.
Mocha: A flexible test framework that requires separate assertion libraries (e.g., Chai) and mocking
libraries.
Workflow: Write test files ( .test.js or .spec.js ), run the test runner from the command line ( npm test ).
Purpose: Provide functions to assert that certain conditions are met during tests (e.g.,
expect(result).toBe(expected) ).
Purpose: Replace real dependencies with mock versions during testing to isolate the code under test.
V. Version Control
1. Git:
Purpose: Distributed version control system for tracking changes in source code during software
development. Essential for collaboration and managing code history.
Workflow: git add , git commit , git push , git pull , git branch , git merge .
Platforms: GitHub, GitLab, Bitbucket provide hosting and collaboration features built on Git.
The heart of a JavaScript project. It defines project metadata, scripts, and dependencies (runtime and
development).
2. TypeScript:
Purpose: A superset of JavaScript that adds static typing. It compiles down to plain JavaScript.
Benefit: Catches errors at compile time rather than runtime, improves code maintainability, and provides
excellent tooling support (autocompletion, refactoring).
Workflow: Write .ts files, transpile with tsc (TypeScript compiler) or Babel, integrate with bundlers.
Purpose: Provide a local server for development with features like hot module replacement (HMR), live
reloading, and proxying API requests.
4. Debugging Tools:
Browser Developer Tools: Chrome DevTools, Firefox Developer Tools. Essential for debugging front-end
JavaScript (breakpoints, console, network tab, performance profilers).
Node.js Debugger: Built-in debugger ( node --inspect ) or integrated debuggers in IDEs (VS Code).
Workflow: Whenever code is pushed to a repository, the CI system runs tests, lints code, builds the
project, and if all passes, deploys it.
Javascript Notes 66
Typical Modern JavaScript Workflow:
1. Project Setup:
2. Development Loop:
Save files (IDE often auto-formats with Prettier and lints with ESLint).
Run npm run start (or similar) to fire up the dev server with HMR.
3. Testing:
4. Version Control:
Run npm run build to create optimized, transpiled, and bundled production assets.
In web browsers, the primary mechanism for interacting with APIs is through HTTP requests.
Uses methods like GET (retrieve data), POST (send data), PUT (update/replace data), PATCH (partially
update data), DELETE (remove data).
Status Codes: Indicate the result of an HTTP request (e.g., 200 OK, 201 Created, 400 Bad Request, 404
Not Found, 500 Internal Server Error).
The most common data format for exchanging data between a web browser and a server.
3. Asynchronous Operations:
API calls are inherently asynchronous because they involve waiting for a response from a remote server
(which takes time).
Javascript Notes 67
JavaScript handles this using Promises and async/await to prevent the browser from freezing while waiting
for data.
} catch (error) {
console.error('Error fetching posts:', error);
// You might update the UI to show an error message
}
}
fetchPosts();
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST', // Specify the HTTP method
headers: {
'Content-Type': 'application/json', // Tell the server we're sending JSON
},
body: JSON.stringify(newPost), // Convert JavaScript object to JSON string
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const createdPost = await response.json(); // Server often returns the created resource
console.log('Created Post:', createdPost);
} catch (error) {
console.error('Error creating post:', error);
Javascript Notes 68
}
}
createPost();
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'PATCH', // Or 'PUT' for full replacement
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updatedData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error) {
console.error('Error updating post:', error);
}
}
} catch (error) {
console.error('Error deleting post:', error);
}
}
Javascript Notes 69
Axios is a Promise-based HTTP client for the browser and Node.js. It's widely used because it offers some
convenient features out-of-the-box that Fetch API doesn't, such as automatic JSON parsing/stringifying, better
error handling, and request/response interceptors.
First, you need to install it: npm install axios or yarn add axios
} catch (error) {
// Axios provides more detailed error objects
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error('Data:', error.response.data);
console.error('Status:', error.response.status);
console.error('Headers:', error.response.headers);
} else if (error.request) {
// The request was made but no response was received
console.error('No response received:', error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error message:', error.message);
}
console.error('Config:', error.config);
}
}
// fetchUsersAxios();
try {
const response = await axios.post('https://jsonplaceholder.typicode.com/users', newUser);
console.log('Created User (Axios):', response.data);
} catch (error) {
console.error('Error creating user (Axios):', error.message);
}
}
// createUserAxios();
Error Handling:
Always check response.ok (for Fetch) or rely on catch block (for Axios) to detect non-2xx HTTP status codes.
Javascript Notes 70
Loading States: Inform the user when data is being fetched (e.g., display a spinner or "Loading..." message).
Hide it when the data arrives or an error occurs.
Data Validation: Validate data received from the API, as you cannot always trust external sources.
Authentication/Authorization: For protected APIs, include API keys or JWT tokens in headers (e.g.,
Authorization: Bearer YOUR_TOKEN ).
Rate Limiting: Be aware of API rate limits to avoid being blocked. Implement retry logic with exponential
backoff if necessary.
CORS (Cross-Origin Resource Sharing): Be aware of CORS issues when making requests to different
domains. The server needs to send appropriate CORS headers.
Environment Variables: Store API keys and sensitive information in environment variables (not directly in
client-side code).
Abstraction (Service Layer): For larger applications, create a dedicated "service" or "API" layer to
encapsulate all API calls, making your components cleaner and easier to manage.
loadAndDisplayPosts();
By following these principles and utilizing Fetch or Axios with async/await , you can effectively integrate external data
and functionality into your JavaScript applications.
Javascript Notes 71