0% found this document useful (0 votes)
16 views71 pages

Javascript_Notes

The document provides an overview of JavaScript, including its history, role in web development, and key concepts like variables and data types. It details the evolution of JavaScript from its creation in 1995 to its current status as a core web technology, highlighting features like interactivity, dynamic content manipulation, and asynchronous communication. Additionally, it explains variable declaration using var, let, and const, and categorizes JavaScript data types into primitive and non-primitive types.

Uploaded by

vision satya
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)
16 views71 pages

Javascript_Notes

The document provides an overview of JavaScript, including its history, role in web development, and key concepts like variables and data types. It details the evolution of JavaScript from its creation in 1995 to its current status as a core web technology, highlighting features like interactivity, dynamic content manipulation, and asynchronous communication. Additionally, it explains variable declaration using var, let, and const, and categorizes JavaScript data types into primitive and non-primitive types.

Uploaded by

vision satya
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/ 71

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.

2009: Node.js was released, allowing JavaScript to run on servers.

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.

2. Role of JavaScript in Web Development


JavaScript is one of the core technologies of the web, alongside HTML and CSS. Its primary role is to make web
pages interactive, dynamic, and functional. Here's how:

🔹 1. Interactivity
Adds interactive features like dropdowns, sliders, modal windows, and form validation.

Responds to user actions (clicks, hovers, keystrokes) without reloading the page.

🔹 2. Dynamic Content Manipulation


Updates HTML and CSS in real time using the DOM (Document Object Model).

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.

Reduces the load on the server and speeds up user experience.

🔹 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.

🔹 6. Framework and Library Support


Powers modern frameworks like React, Angular, and Vue.js, which simplify building complex UIs.

Rich ecosystem of libraries for animations, charts, and data processing.

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.

Here's a breakdown of key aspects of a JavaScript variable:

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.

: Declares a block-scoped read-only named constant. Once a value is assigned, it cannot be


const

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:

Store user input.

Keep track of calculations.

Control the flow of your program based on conditions.14

Store elements from a web page.15

And much more!

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

console.log(userName); // Output: Alice


console.log(age); // Output: 30

userName = "Bob"; // Reassigning a new value to userName (possible with let)


// PI = 3.14; // This would cause an error because PI is a const

1. var (Legacy/Function-Scoped)
Scope: var is function-scoped or globally scoped. This means:

If declared inside a function, it's accessible anywhere within that function.

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.

Example with var :

// 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
}

console.log(x); // Output: 10 (x is still accessible outside the if block)

var y = 20;
console.log(y); // Output: 20

var y = 30; // Redeclaration is allowed (and overwrites the previous value)


console.log(y); // Output: 30

console.log(z); // Output: undefined (hoisted, but not yet assigned)


var z = 40;
console.log(z); // Output: 40
}

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 .

Reassignment: You can reassign a value to a let variable.

Redeclaration: You cannot redeclare a let variable within the same scope. Attempting to do so will result in a
SyntaxError .

Example with let :

let globalLet = "I'm global (but not a property of window)";

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
}

// console.log(b); // ReferenceError: b is not defined (b is not accessible outside the if block)

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();

3. const (Modern/Block-Scoped, Read-Only)


Scope: const is also block-scoped, just like let.

Hoisting: Similar to let , const declarations are hoisted but are in a "temporal dead zone." Accessing a const

variable before its declaration will result in a ReferenceError .

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.

Example with const :

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
}

// console.log(country); // ReferenceError: country is not defined

// const city = "Delhi"; // SyntaxError: Identifier 'city' has already been declared (redeclaration not allowed)

const myArray = [1, 2, 3];


myArray.push(4); // Allowed! The array's contents can be modified
console.log(myArray); // Output: [1, 2, 3, 4]

// myArray = [5, 6]; // TypeError: Assignment to constant variable. (reassignment of the array itself is not allowe

const myObject = { name: "Rajesh", age: 25 };


myObject.age = 26; // Allowed! Object properties can be modified
console.log(myObject); // Output: { name: 'Rajesh', age: 26 }

// myObject = { name: "Priya" }; // TypeError: Assignment to constant variable. (reassignment of the object itsel
}

exampleConst();

Summary Table:

Javascript Notes 4
Feature var let const

Scope Function/Global Block Block

Hoisting Yes (initialized undefined ) Yes (temporal dead zone) Yes (temporal dead zone)

Reassignment Yes Yes No (must initialize)

Redeclaration Yes No No

Best Use Case Legacy code only Variables that will change Variables that should remain constant

Modern JavaScript Best Practices:


In modern JavaScript development (ES6 and beyond), it's highly recommended to avoid using var due to its
unpredictable scoping and redeclaration issues.

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.

4. Data Types in Javascript


JavaScript is a dynamically typed language, meaning you don't explicitly declare the data type of a variable. The
type is determined automatically at runtime based on the value assigned to it.

JavaScript data types are broadly categorized into two groups:

1. Primitive Data Types


Primitive data types represent single, simple values. They are immutable, meaning their values cannot be changed
once created (though the variable holding the primitive value can be reassigned to a new primitive value).
There are seven primitive data types in JavaScript:

Number :

Represents both integer and floating-point numbers.

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 :

Represents textual data.

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 :

Represents a logical entity.

Has only two possible values: true or false .

Often used for conditional logic and comparisons.

Example: let isActive = true; , let isOver18 = false;

Undefined :

Represents a variable that has been declared but has not yet been assigned a value.

It's the default value for uninitialized variables.

Example: let myVariable; // myVariable is undefined

Javascript Notes 5
Null :

Represents the intentional absence of any object value.

It's a primitive value, despite typeof null returning 'object' (which is a long-standing bug in JavaScript).

Example: let user = null; // user has no value explicitly assigned

Symbol :

Introduced in ES6 (ECMAScript 2015).

Represents a unique and immutable value.

Often used to create unique object property keys to avoid name clashes.

Example: const id = Symbol('id'); , const anotherId = Symbol('id'); // id !== anotherId

BigInt :

Introduced in ES2020.

Represents whole numbers larger than 2^53 - 1 (the maximum safe integer that Number can represent).

BigInts are created by appending n to the end of an integer literal.

Example: const largeNumber = 9007199254740991n;

2. Non-Primitive (Reference) Data Types


Non-primitive data types (also known as reference types) are more complex and can store collections of data.
Unlike primitives, they are mutable, meaning their contents can be changed even if the variable holding the
reference isn't reassigned. When you assign a non-primitive value to another variable, you're essentially copying
a reference to the same underlying data in memory.

There is typically one main non-primitive data type:

Object :

The most fundamental non-primitive data type.

Represents a collection of key-value pairs (properties).

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, ...).

Example: let colors = ["red", "green", "blue"];

Functions are also a special type of object in JavaScript (they are "first-class citizens" and can be treated
like any other value).

Example: function greet() { console.log("Hello!"); }

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

console.log(typeof 10); // "number"


console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (this is a known JavaScript quirk/bug)

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.

Representation: This 64-bit format allocates:

1 bit for the sign (positive or negative).

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

console.log(0.1 + 0.2); // Output: 0.30000000000000004 (not exactly 0.3)


console.log(0.1 + 0.7); // Output: 0.7999999999999999

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.

2. Special Numeric Values


The Number type also includes several special values:

Infinity: Represents mathematical infinity (infty). You get this when a number exceeds the largest representable
finite number.

Number.POSITIVE_INFINITY is the same as Infinity .

Example: 1/0 results in Infinity .

Javascript Notes 7
Infinity : Represents negative infinity (−infty).

Number.NEGATIVE_INFINITY is the same as Infinity .

Example: - 1 / 0 results in Infinity .

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

Example: "hello" / 2 results in NaN .

console.log(1 / 0); // Infinity


console.log(-1 / 0); // -Infinity
console.log("abc" / 5); // NaN
console.log(0 / 0); // NaN
console.log(NaN === NaN); // false (important distinction!)

3. BigInt (Arbitrary-Precision Integers)


Introduced in ES2020: To address the limitations of the Number type for very large integers, JavaScript
introduced the BigInt primitive data type.

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.

const hugeNumber = 9007199254740991n; // A BigInt


const evenLargerNumber = hugeNumber + 10n;
console.log(evenLargerNumber); // 9007199254741001n

// 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

4. Number Literals (Different Bases)


You can write numbers in different bases:

Decimal (Base 10): Standard numbers. 123 , 3.14 .

Hexadecimal (Base 16): Prefixed with 0x or 0X .

0xFF (255 decimal)

Octal (Base 8): Prefixed with 0o or 0O (strict mode requires this, older non-strict mode might interpret 0

followed by digits as octal).

0o377 (255 decimal)

Binary (Base 2): Prefixed with 0b or 0B .

0b11111111 (255 decimal)

Scientific (Exponent) Notation: For very large or very small numbers.

1.2e5 (120000)

3e-4 (0.0003)

5. Number Object and Methods


While numbers are primitive values, JavaScript also has a Number object (a wrapper object). When you perform
operations or call methods on a primitive number, JavaScript temporarily converts it to a Number object.
Useful Number methods and properties:

Number.isFinite(value) : Checks if a value is a finite number (not Infinity , Infinity , or NaN ).

Number.isInteger(value) : Checks if a value is an integer.

Javascript Notes 8
Number.isNaN(value) : Checks if a value is NaN . (More reliable than value === NaN ).

toFixed(digits) : Formats a number using fixed-point notation (as a string).

toPrecision(precision) : Formats a number to a specified length (as a string).

toString(radix) : Converts a number to a string in a specified base.

parseFloat(string) : Parses a string argument and returns a floating-point number.

parseInt(string, radix) : Parses a string argument and returns an integer of the specified radix (base).

JavaScript

let num = 123.45678;


console.log(num.toFixed(2)); // "123.46" (rounds)
console.log(num.toPrecision(5)); // "123.46" (total 5 digits)
console.log(num.toString(16)); // "7b.74c" (hexadecimal string representation)

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:

Single Quotes ( '' ):JavaScript

let name = 'Alice';

Double Quotes ( "" ):JavaScript

let greeting = "Hello, world!";

Backticks (` - Template Literals):


Introduced in ES6 (ECMAScript 2015), template literals offer more flexibility.

Multi-line strings: You can write strings spanning multiple lines without needing escape characters.

let poem = `Roses are red,


Violets are blue,
JavaScript is fun,
And so are you!`;
console.log(poem);

String Interpolation: You can embed expressions (variables, function calls, arithmetic operations, etc.)
directly within the string using the ${} syntax.

let user = "Bob";


let age = 25;
let message = `Hi, my name is ${user} and I am ${age} years old.`;
console.log(message); // Output: Hi, my name is Bob and I am 25 years old.

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.

let originalString = "Hello";


let newString = originalString.toUpperCase();

console.log(originalString); // Output: "Hello" (original unchanged)


console.log(newString); // Output: "HELLO" (new string created)

3. String Properties and Methods


Strings in JavaScript are treated like "primitive objects" and have many built-in properties and methods that allow
you to manipulate and work with textual data.

Common Properties:
length : Returns the number of characters in the string.

let text = "JavaScript";


console.log(text.length); // Output: 10

Common Methods:
Accessing Characters:

[index] (bracket notation): Accesses a character at a specific index.

charAt(index) : Also accesses a character at a specific index.

let str = "coding";


console.log(str[0]); // Output: "c"
console.log(str.charAt(3)); // Output: "i"

Changing Case:

toLowerCase() : Converts the string to lowercase.

toUpperCase() : Converts the string to uppercase.

let mixedCase = "Hello World";


console.log(mixedCase.toLowerCase()); // Output: "hello world"
console.log(mixedCase.toUpperCase()); // Output: "HELLO WORLD"

Searching for Substrings:

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).

startsWith(substring) : Returns true if the string starts with the substring.

endsWith(substring) : Returns true if the string ends with the substring.

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:

: Extracts a part of a string and returns a new string.


slice(startIndex, endIndex) endIndex is exclusive. Negative
indices count from the end.

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 ).

let part = "JavaScript";


console.log(part.slice(0, 4)); // Output: "Java"
console.log(part.substring(4, 10)); // Output: "Script"
console.log(part.slice(-6)); // Output: "Script"

Replacing Content:

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

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

Can also use regular expressions for more complex replacements.

let textToReplace = "Dog, Cat, Dog";


console.log(textToReplace.replace("Dog", "Fox")); // Output: "Fox, Cat, Dog"
console.log(textToReplace.replaceAll("Dog", "Fox")); // Output: "Fox, Cat, Fox"

Trimming Whitespace:

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

trimStart() : Removes whitespace from the beginning (ES2019).

trimEnd() : Removes whitespace from the end (ES2019).

let padded = " Hello World ";


console.log(padded.trim()); // Output: "Hello World"

Splitting and Joining:

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

join(separator) (Array method): Joins elements of an array into a string.

let fruits = "apple,banana,orange";


let fruitArray = fruits.split(',');
console.log(fruitArray); // Output: ["apple", "banana", "orange"]

let newString = fruitArray.join(' - ');


console.log(newString); // Output: "apple - banana - orange"

Concatenation:

Using the + operator.

Using the concat() method.

let str1 = "Hello";


let str2 = "World";
let combined = str1 + " " + str2; // Preferred

Javascript Notes 11
console.log(combined); // Output: "Hello World"

let combined2 = str1.concat(" ", str2, "!");


console.log(combined2); // 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:

\' : Single quote

\" : Double quote

\\ : Backslash

\n : Newline

\t : Tab

\r : Carriage return

let path = "C:\\Program Files\\MyFolder";


let quote = "He said, \"Hello!\"";
let multiLine = "Line 1\nLine 2";

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.

console.log("The answer is " + 42); // Output: "The answer is 42"


console.log("Is active? " + true); // Output: "Is active? true"

Template literals: All embedded expressions are converted to strings.

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:

true : Represents a positive, affirmative, or valid condition.

false : Represents a negative, unfulfilled, or invalid condition.

2. Creating Booleans
You typically don't explicitly create Boolean objects using new Boolean() . Instead, you assign the literal values true or
false directly.

let isActive = true;


let hasPermission = false;

Javascript Notes 12
let isLoggedIn = true;

3. Boolean Coercion (Truthy and Falsy Values)


This is one of the most crucial concepts related to Booleans in JavaScript. In many contexts, JavaScript
automatically converts values of other data types into Booleans. This process is called type coercion.
When a non-boolean value is evaluated in a boolean context (e.g., inside an if statement, a while loop condition,
or with logical operators like && or || ), it's converted to either true or false .
Falsy Values:

There are a specific set of values that, when coerced to a boolean, become false. These are known as falsy
values:

false (the boolean literal itself)

0 (the number zero)

0 (negative zero)

"" (an empty string)

null

undefined

NaN (Not-a-Number)

0n (BigInt zero - introduced with BigInt)

Truthy Values:
Any value that is not one of the falsy values listed above is considered truthy and will coerce to true.

Examples of truthy values:

true (the boolean literal itself)

Any non-zero number (e.g., 1 , 10 , 3.14 )

Any non-empty string (e.g., "hello" , "false" , "" )

Arrays (even empty ones, [] )

Objects (even empty ones, {} )

Functions

Infinity , Infinity

Examples of Boolean Coercion:1

// 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).");
}

4. Explicit Conversion to Boolean


You can explicitly convert a value to its boolean equivalent using:

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

5. Operators that Return Booleans


Several operators in JavaScript yield boolean values:

Comparison Operators:

== (loose equality)

=== (strict equality)

!= (loose inequality)

!== (strict inequality)

< , > , <= , >=

console.log(5 > 3); // true


console.log("2" == 2); // true (loose equality, type coercion)
console.log("2" === 2); // false (strict equality, no type coercion)

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.

! (Logical NOT): Inverts the boolean value of its operand.

let a = true;
let b = false;

console.log(a && b); // false


console.log(a || b); // true
console.log(!a); // false
console.log(!0); // true (0 is falsy, !0 is true)
console.log(!"hello"); // false ("hello" is truthy, !"hello" is false)

Javascript Notes 14
6. Usage of Booleans
Booleans are fundamental for:

Conditional Statements: Controlling if , else if , else blocks.

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++;
}

Flag Variables: Toggling states in an application.

let dataLoaded = false;


// ... data loading process
if (success) {
dataLoaded = true;
}

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.

What undefined Signifies:


undefined typically signifies one of the following scenarios:

1. Variable Declared But Not Assigned a Value:

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

2. Function Parameters Not Provided (Missing Arguments):


If a function is called and an argument for a parameter is not provided, that parameter will have the value
undefined inside the function's body.

Javascript Notes 15
function greet(name) {
console.log(`Hello, ${name}!`);
}

greet("Alice"); // Output: Hello, Alice!


greet(); // Output: Hello, undefined!

3. Missing Object Properties:


If you try to access a property on an object that does not exist, the result will be undefined.

const person = {
name: "Bob",
age: 30
};

console.log(person.name); // Output: Bob


console.log(person.city); // Output: undefined (city property does not exist)

4. Return Value of Functions That Don't Explicitly Return Anything:


If a function does not have a return statement, or if it has a return; statement without a specified value, it
implicitly returns undefined.

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.

console.log(void(0)); // Output: undefined


console.log(void("hello")); // Output: undefined

undefined vs. null


This is a very common point of confusion for beginners. While both undefined and null represent an "absence of
value," they have distinct semantic meanings:

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:

Feature undefined null

Intentional absence of object value (programmer


Meaning Value not assigned/defined (default)
assigned)

Type ( typeof ) "undefined" "object" (a historical bug in JS)

Coercion to Number NaN 0

Javascript Notes 16
Equality undefined == null is true null == undefined is true

undefined === null is false (strict) null === undefined is false (strict)

Example:

let myVar; // myVar is undefined


console.log(typeof myVar); // "undefined"

let myObject = null; // myObject is explicitly set to null


console.log(typeof myObject); // "object" (the typeof null bug)

console.log(myVar == myObject); // true (loose equality)


console.log(myVar === myObject); // false (strict equality, different types)

Checking for undefined


You can check if a variable or property is undefined using:

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.");
}

// Checking a non-existent global variable without throwing an error


if (typeof nonExistentVar === 'undefined') {
console.log("nonExistentVar is not defined.");
}

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.

Key Characteristics of null :


1. Primitive Value: null is one of JavaScript's primitive data types.

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.

console.log(typeof null); // Output: "object" (this is a known quirk)

When is null Used?


Initializing Variables: When you declare a variable that you intend to hold an object later, but you want to
explicitly state that it's currently empty or has no value.

let user = null; // user is intentionally set to no object value


// Later in the code...
// user = fetchUserDetails();

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.

let largeDataObject = { /* ... a lot of data ... */ };


// After processing
largeDataObject = null; // Clear the reference, allowing garbage collection

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
}
}

let foundUser = findUserById(456);


if (foundUser === null) {
console.log("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.

const myElement = document.getElementById('nonExistentId');


console.log(myElement); // Output: null (if element not found)

null vs. undefined (Revisited)


As discussed in the undefined explanation, the distinction is crucial:

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:

Feature null undefined

Javascript Notes 18
Intentional absence of object value (programmer
Meaning Value not assigned/defined (default)
assigned)

Type ( typeof ) "object" (historical bug) "undefined"

Coercion to Number 0 NaN

Equality null == undefined is true undefined == null is true

null === undefined is false (strict) undefined === null is false (strict)

Checking for null


1. Strict Equality ( === ): This is the recommended way to check specifically for null , as it avoids type coercion.

let myValue = null;


if (myValue === null) {
console.log("myValue is strictly null.");
}

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 .

let myValue = undefined; // Or null


if (myValue == null) { // This will be true for both null and undefined
console.log("myValue is null or undefined.");
}

3. Falsy Check (using ! or Boolean() ): Since null is a falsy value, it will evaluate to false in a boolean context.

let myData = null;


if (!myData) { // This condition is true if myData is null, 0, "", undefined, NaN, etc.
console.log("myData is falsy.");
}

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.

Key Characteristics of Symbols:


1. Uniqueness: Every time you create a Symbol, even with the same description, it is guaranteed to be
unique.

const id1 = Symbol('id');


const id2 = Symbol('id');

console.log(id1 === id2); // Output: false (they are unique)

2. Immutability: Once a Symbol is created, its value cannot be changed.

3. Primitive Data Type: Like number , string , boolean , null , undefined , and bigint , Symbol is a primitive. Its typeof

result is "symbol" .

const mySymbol = Symbol('description');


console.log(typeof mySymbol); // Output: "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"
};

for (let key in myObject) {


console.log(key); // Output: "name" (Symbol is skipped)
}

console.log(Object.keys(myObject)); // Output: ["name"]

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).

Symbol() : Creates a new unique Symbol.

const uniqueKey = Symbol();

: 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.

const userId = Symbol('user_id');


const role = Symbol('role');

console.log(userId.description); // Output: "user_id"

Using Symbols as Object Property Keys:


The primary use case for Symbols is as property keys in objects.

const PRIVATE_DATA = Symbol('privateData');


const STATUS_MESSAGE = Symbol('statusMessage');

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];
}

// A method that might update the status,


// but status itself is not easily enumerated or overwritten accidentally
updateStatus(newStatus) {
this[STATUS_MESSAGE] = newStatus;
}
}

const user = new User("John Doe", { email: "john@example.com" });

console.log(user.name); // "John Doe"


console.log(user[PRIVATE_DATA]);// { email: "john@example.com" }

// You can't easily iterate over PRIVATE_DATA or STATUS_MESSAGE


for (let key in user) {

Javascript Notes 20
console.log(key); // Only 'name' will be logged
}

console.log(Object.keys(user)); // Only ['name']

// To access Symbol properties, you need the Symbol itself


console.log(user[STATUS_MESSAGE]); // "Online"

Retrieving Symbol Properties:


While Symbol properties are not enumerable by for...in or Object.keys() , there are specific methods to retrieve
them:

Object.getOwnPropertySymbols(obj) : Returns an array of all Symbol properties found directly on a given object.

const userSymbols = Object.getOwnPropertySymbols(user);


console.log(userSymbols); // Output: [Symbol(privateData), Symbol(statusMessage)]
console.log(user[userSymbols[0]]); // Accessing the first Symbol property

Reflect.ownKeys(obj) : Returns an array containing both string and Symbol properties of an object.

console.log(Reflect.ownKeys(user)); // Output: ["name", Symbol(privateData), Symbol(statusMessage)]

Global Symbol Registry ( Symbol.for() and Symbol.keyFor() ):


Sometimes you might want to create Symbols that are unique but also reusable across different parts of your
application or even different realms (e.g., iframes). The global Symbol registry allows you to do this.

Symbol.for(key) :JavaScript

Looks for a Symbol with the given key in the global Symbol registry.

If found, it returns that Symbol.

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.

const sym1 = Symbol.for('sharedKey'); // Creates if not exists, otherwise retrieves


const sym2 = Symbol.for('sharedKey'); // Retrieves the same Symbol

console.log(sym1 === sym2); // Output: true


console.log(sym1); // Output: Symbol(sharedKey)

Symbol.keyFor(symbol) :

Takes a Symbol as an argument.

If the Symbol was created using Symbol.for() , it returns the string key (description) associated with it in
the global registry.

If the Symbol was created using Symbol() , it returns undefined .

const sharedSym = Symbol.for('myGlobalSymbol');


const localSym = Symbol('myLocalSymbol');

console.log(Symbol.keyFor(sharedSym)); // Output: "myGlobalSymbol"


console.log(Symbol.keyFor(localSym)); // Output: undefined

Well-Known Symbols (Built-in Symbols):


JavaScript also provides a set of "well-known Symbols" that are built-in and used by the language itself to
define internal behavior. These are properties of the global Symbol object. Examples include:

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() ).

Symbol.hasInstance : Used to define the behavior of the instanceof operator.

Symbol.toPrimitive : Used to define how an object is converted to a primitive value.

These well-known Symbols allow developers to customize the behavior of their objects in ways that were
previously not possible or required hacks.

Use Cases for Symbols:


Adding "hidden" properties to objects: Useful for metadata or internal state that shouldn't be easily
discoverable or accidentally overwritten.

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.

The Problem BigInt Solves


The standard Number type in JavaScript uses double-precision 64-bit floating-point format (IEEE 754). This
format is excellent for handling both integers and decimals, and it can represent numbers up to about
1.79times10308.
However, the precision for integers is limited. The maximum "safe" integer that can be precisely represented is
253−1. This value is available as Number.MAX_SAFE_INTEGER , which is 9,007,199,254,740,991 .
When you perform arithmetic with integers larger than Number.MAX_SAFE_INTEGER (or smaller than
Number.MIN_SAFE_INTEGER ), you risk losing precision. This means that numbers that should be distinct might be
represented as the same value, leading to incorrect calculations.

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

Working with large IDs from databases

Financial calculations involving very large whole numbers

Scientific calculations requiring exact large integer results

BigInt was created to solve this.

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.

How to Create a BigInt


You can create a BigInt in two primary ways:

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"

const anotherBigInt = 9007199254740991n; // Even safe integers can be BigInts


const negativeBigInt = -50n;

2. Use the BigInt() constructor function: You can pass a Number or a String representing an integer to the
BigInt() constructor.

const fromNumber = BigInt(10); // Output: 10n


const fromString = BigInt("12345678901234567890"); // Output: 12345678901234567890n
// const fromBoolean = BigInt(true); // Error: Cannot convert true to a BigInt

Operations with BigInt


Arithmetic Operators: Most standard arithmetic operators work with BigInt s: + ,,, / , % , * .

const a = 10n;
const b = 5n;

console.log(a + b); // 15n


console.log(a * b); // 50n
console.log(a / b); // 2n (BigInt division truncates decimals)
console.log(a % b); // 0n
console.log(a ** b); // 100000n

Comparison Operators: BigInt s can be compared with other BigInt s or even Number s using comparison
operators ( < , > , <= , >= , == , === , != , !== ).

console.log(10n > 5n); // true


console.log(10n == 10); // true (loose equality performs type coercion)
console.log(10n === 10); // false (strict equality, different types)

Bitwise Operators: Bitwise operators ( & , | , ^ , ~ , << , >> , >>> ) also work with BigInt s.

console.log(5n | 3n); // 7n (binary 101 | 011 = 111)

Important Considerations / Pitfalls


1. No Mixed Operations (Directly): You cannot directly mix BigInt s with Number s in arithmetic operations.
Doing so will throw a TypeError . You must explicitly convert one of the operands.

const num = 10;


const big = 5n;

// 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.

console.log(10n / 3n); // Output: 3n (not 3.333...n, just 3)


console.log(11n / 2n); // Output: 5n

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"
};

// JSON.stringify(myData); // TypeError: Do not know how to serialize a BigInt

// Workaround: Convert to string before serializing, then back when parsing


const dataToSend = {
id: String(123n),
name: "Test"
};
console.log(JSON.stringify(dataToSend)); // {"id":"123","name":"Test"}

// When parsing, convert back:


const receivedData = JSON.parse('{"id":"123","name":"Test"}');
receivedData.id = BigInt(receivedData.id);
console.log(receivedData.id); // 123n

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");
}

When to Use BigInt


When you need to perform calculations with integers larger than Number.MAX_SAFE_INTEGER (253−1) or smaller
than Number.MIN_SAFE_INTEGER (−(253−1)) and require absolute precision.

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;

+= (Addition assignment): x=x+y

Javascript Notes 24
= (Subtraction assignment): x=x-y

= (Multiplication assignment): x=x*y

/= (Division assignment): x=x/y

%= (Remainder assignment): x=x%y

*= (Exponentiation assignment): x = x ** y (ES2016)

<<= (Left shift assignment)

>>= (Right shift assignment)

>>>= (Unsigned right shift assignment)

&= (Bitwise AND assignment)

|= (Bitwise OR assignment)

^= (Bitwise XOR assignment)

&&= (Logical AND assignment): Assigns if left is truthy (ES2021)

||= (Logical OR assignment): Assigns if left is falsy (ES2021)

??= (Nullish coalescing assignment): Assigns if left is nullish (ES2021)

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)

% (Remainder/Modulo): Returns the integer remainder of a division.

* (Exponentiation): x raised to the power of y (ES2016).

let num1 = 10;


let num2 = 3;
console.log(num1 + num2); // 13
console.log(num1 % num2); // 1 (10 divided by 3 is 3 with remainder 1)
console.log(2 ** 3); // 8 (2 * 2 * 2)

++ (Increment): Increases the value by 1.

Prefix: ++x (increments then returns the new value)

Postfix: x++ (returns the original value then increments)

- (Decrement): Decreases the value by 1.

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.

> (Greater than)

< (Less than)

>= (Greater than or equal to)

<= (Less than or equal to)

console.log(5 == "5"); // true (loose equality, string "5" is coerced to number 5)


console.log(5 === "5"); // false (strict equality, different types)
console.log(10 > 5); // true
console.log(null == undefined); // true (quirk: they are loosely equal)
console.log(null === undefined); // false

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
||

encountered, or the last operand if all are falsy.

! (Logical NOT): Inverts the boolean value of its operand.

let age = 20;


let hasLicense = true;
console.log(age >= 18 && hasLicense); // true
console.log(age < 18 || hasLicense); // true
console.log(!hasLicense); // false

console.log(0 || "hello"); // "hello" (0 is falsy, "hello" is truthy)


console.log("" && "world"); // "" ("" is falsy)

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 AND)

| (Bitwise OR)

^ (Bitwise XOR)

~ (Bitwise NOT)

<< (Left shift)

>> (Right shift)

>>> (Unsigned right shift)

console.log(5 & 1); // 1 (binary 101 & 001 = 001)

6. String Operators
Primarily used for string concatenation.

Javascript Notes 26
+ (Concatenation): Used to join two or more strings.

let firstName = "John";


let lastName = "Doe";
let fullName = firstName + " " + lastName; // "John Doe"

+= (Concatenation assignment): x=x+y for strings.

let message = "Hello";


message += " World"; // "Hello World"

7. Ternary (Conditional) Operator


The only JavaScript operator that takes three operands. It's a shorthand for an if-else statement.

condition ? expressionIfTrue : expressionIfFalse

let isAdult = (age >= 18) ? "Yes" : "No";


console.log(isAdult); // "Yes" if age is 20

8. Unary Operators
Operate on a single operand.

+ (Unary plus): Attempts to convert the operand to a number.

(Unary negation): Converts to a number and negates it.

! (Logical NOT)

typeof : Returns a string indicating the data type of its operand.

delete : Deletes an object's property or an element from an array.

void : Evaluates an expression and returns undefined .

console.log(+"5"); // 5 (number)
console.log(-"10"); // -10 (number)
console.log(typeof "hello"); // "string"

9. Relational (Type-Checking) Operators


instanceof : Checks if an object is an instance of a particular constructor function.

class Car {}
let myCar = new Car();
console.log(myCar instanceof Car); // true
console.log([] instanceof Array); // true

in : Checks if a property exists in an object (or its prototype chain).

const person = { name: "Alice", age: 30 };


console.log("name" in person); // true
console.log("city" in person); // false

10. Spread/Rest Operator ( ... ) (ES6)


While technically part of syntax, it functions as an operator in several contexts:

Spread Syntax: Expands an iterable (like an array or string) into individual elements.

In array literals: [...arr1, ...arr2]

In function calls: myFunction(...args)

In object literals: { ...obj1, ...obj2 } (ES2018)

Javascript Notes 27
Rest Parameters: Gathers an indefinite number of arguments into an array.

In function definitions: function func(...args)

const arr1 = [1, 2];


const arr2 = [...arr1, 3, 4]; // [1, 2, 3, 4]

function sum(...numbers) {
return numbers.reduce((acc, num) => acc + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

11. Nullish Coalescing Operator ( ?? ) (ES2020)


?? : Returns the right-hand side operand when the left-hand side operand is null or undefined . Otherwise, it
returns the left-hand side operand. It's a safer alternative to || when you specifically want to avoid 0 or
"" being treated as "empty."

let userSettings = { volume: 0, theme: null, font: undefined };


let defaultVolume = 50;
let defaultTheme = "dark";
let defaultFont = "Arial";

console.log(userSettings.volume ?? defaultVolume); // 0 (0 is not null/undefined)


console.log(userSettings.theme ?? defaultTheme); // "dark" (null is nullish)
console.log(userSettings.font ?? defaultFont); // "Arial" (undefined is nullish)

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:

let age = 20;

if (age >= 18) {


console.log("You are old enough to vote.");
}
// Output: You are old enough to vote.

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:

let temperature = 25;

if (temperature > 30) {


console.log("It's hot outside!");
} else {
console.log("It's not too hot.");
}
// Output: It's not too hot.

3. if...else if...else statement


This statement allows you to test multiple conditions sequentially. It executes the block of code for the first
condition that evaluates to true. If none of the if or else if conditions are true, the else block (if present) is
executed.
Syntax:

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:

let score = 75;

if (score >= 90) {


console.log("Grade: A");
} else if (score >= 80) {
console.log("Grade: B");
} else if (score >= 70) {
console.log("Grade: C");
} else {
console.log("Grade: F");
}
// Output: Grade: C

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:

let day = "Wednesday";

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!

5. Ternary Operator (Conditional Operator)


The ternary operator is a shorthand way to write simple if...else statements. It's the only JavaScript operator that
takes three operands.
Syntax:

condition ? expressionIfTrue : expressionIfFalse;

Example:

let isRaining = true;


let activity = isRaining ? "Stay indoors" : "Go for a walk";

console.log(activity); // Output: Stay indoors

let age = 16;


let status = (age >= 18) ? "Adult" : "Minor";
console.log(status); // Output: Minor

Choosing the Right Conditional Statement


if...else : Use for simple true/false conditions.

if...else if...else : Use for testing multiple, distinct conditions sequentially.

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:

for (initialization; condition; afterthought) {


// code to be executed
}

Explanation:

initialization : Executed once before the loop starts (e.g., let i = 0; ).

condition : Evaluated before each iteration. If true , the loop body executes. If false , the loop terminates.

afterthought : Executed after each iteration (e.g., i++ ).

Example:

for (let i = 0; i < 5; i++) {


console.log("Hello, world! Iteration: " + i);
}
// Output:
// Hello, world! Iteration: 0
// Hello, world! Iteration: 1
// Hello, world! Iteration: 2
// Hello, world! Iteration: 3
// Hello, world! Iteration: 4

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:

Purpose: Iterates over the enumerable properties (keys) of an object.

Syntax:

for (let key in object) {


// code to be executed
}

Example:

const person = {
name: "Alice",
age: 30,
city: "New York"
};

for (let key in person) {


console.log(`${key}: ${person[key]}`);
}
// Output:
// 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:

for (let value of iterable) {


// code to be executed
}

Example:

const fruits = ["apple", "banana", "cherry"];


for (let fruit of fruits) {
console.log(fruit);
}
// Output:

Javascript Notes 32
// apple
// banana
// cherry

const myString = "hello";


for (let char of myString) {
console.log(char);
}
// Output:
// h
// e
// l
// l
// o

Control Statements within Loops:


break : Terminates the loop entirely and transfers control to the statement immediately following the loop.

for (let i = 0; i < 10; i++) {


if (i === 5) {
break; // Loop stops when i is 5
}
console.log(i);
}
// Output: 0 1 2 3 4

continue : Skips the current iteration of the loop and proceeds to the next iteration.

for (let i = 0; i < 5; i++) {


if (i === 2) {
continue; // Skips printing when i is 2
}
console.log(i);
}
// Output: 0 1 3 4

When to use which loop:


for loop: When you know the exact number of iterations or need to iterate a specific range.

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 + "!";
}

// Calling the function


console.log(greet("Alice")); // Output: Hello, Alice!

Key characteristics:

They are "hoisted," meaning they can be called before they are defined in the code.

They create a named function in the global or local scope.

b) Function Expressions
Functions can also be defined as expressions and assigned to a variable.

const sayHi = function(name) {


return "Hi, " + name + "!";
};

// Calling the function


console.log(sayHi("Bob")); // Output: Hi, Bob!

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.

const factorial = function calculateFactorial(n) {


if (n <= 1) {
return 1;
}
return n * calculateFactorial(n - 1);
};
console.log(factorial(5)); // Output: 120

c) Arrow Functions (ES6+)


Arrow functions provide a more concise syntax for writing function expressions. They are particularly useful for
shorter, single-expression functions and for maintaining the this context.

const add = (a, b) => a + b; // Concise for single expression

console.log(add(5, 3)); // Output: 8

const multiply = (a, b) => { // Block body for multiple statements


let result = a * b;
return result;
};

console.log(multiply(4, 6)); // Output: 24

Key characteristics:

No function keyword.

Parentheses are optional for single parameters: param => ...

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.

Cannot be used as constructors ( new keyword).

Do not have their own arguments object.

2. Function Parameters and Arguments


Parameters: The named variables listed in the function definition (e.g., name in function greet(name) ).

Arguments: The actual values passed to the function when it's called (e.g., "Alice" in greet("Alice") ).

Default Parameters (ES6+)


You can set default values for parameters, which will be used if no argument (or undefined ) is provided for that
parameter.

function greetUser(name = "Guest") {


return "Hello, " + name + "!";
}

console.log(greetUser("Charlie")); // Output: Hello, Charlie!


console.log(greetUser()); // Output: Hello, Guest!

Rest Parameters (ES6+)


The rest parameter syntax allows a function to accept an indefinite number of arguments as an array.

function sumAll(...numbers) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}

console.log(sumAll(1, 2, 3)); // Output: 6


console.log(sumAll(10, 20, 30, 40)); // Output: 100

3. Returning Values
Functions can return a value using the return statement. If no return statement is specified, the function implicitly
returns undefined .

function calculateArea(width, height) {


return width * height;
}

let area = calculateArea(10, 5);


console.log(area); // Output: 50

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.

Passed as arguments to other functions.

Returned as values from other functions.

Functions that take other functions as arguments or return functions as results are called higher-order functions.

// Function passed as an argument


function operateOnNumbers(num1, num2, operation) {
return operation(num1, num2);
}

const addOperation = (a, b) => a + b;


const subtractOperation = (a, b) => a - b;

console.log(operateOnNumbers(10, 5, addOperation)); // Output: 15


console.log(operateOnNumbers(10, 5, subtractOperation)); // Output: 5

// Function returning another function (closure example)


function multiplier(factor) {
return function(number) {
return number * factor;
};
}

const double = multiplier(2);


const triple = multiplier(3);

console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15

6. Immediately Invoked Function Expressions (IIFE)


An IIFE is a function expression that is executed immediately after it is defined. They are often used to create a
private scope for variables, avoiding polluting the global namespace.

(function() {
let privateVariable = "This is private.";
console.log(privateVariable); // Output: This is private.
})();

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

7. The arguments Object

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]);
}

showArguments("apple", 123, true);


// Output: [Arguments] { '0': 'apple', '1': 123, '2': true }
// Output: apple

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:

Modularity: Breaking down complex problems into smaller, manageable pieces.

Reusability: Writing code once and using it multiple times.

Maintainability: Easier to debug and update specific parts of the code.

Abstraction: Hiding complex logic behind a simple function interface.

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).

Key Concepts of JavaScript Objects:


1. Collection of Properties: Objects are containers for properties. Each property consists of a key (a string or
Symbol) and a value.

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 new property


car.year = 2020;
console.log(car); // { make: "Toyota", model: "Camry", year: 2020 }

// Modify an existing property


car.model = "Corolla";
console.log(car); // { make: "Toyota", model: "Corolla", year: 2020 }

// 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

How to Create Objects:


There are several common ways to create objects:

1. Object Literal Syntax (most common):


This is the simplest and most widely used way to create objects.

const user = {
firstName: "John",
lastName: "Doe",
age: 25,
email: "john.doe@example.com"
};

2. Using new Object() (less common for plain objects):

Javascript Notes 38
You can use the Object constructor.

const anotherUser = new Object();


anotherUser.firstName = "Jane";
anotherUser.lastName = "Smith";
console.log(anotherUser); // { firstName: "Jane", lastName: "Smith" }

3. Constructor Functions:
Used when you need to create multiple objects of the same "type" or "blueprint."

function Person(name, age) {


this.name = name;
this.age = age;
this.greet = function() {
console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`);
};
}

const person1 = new Person("Alice", 30);


const person2 = new Person("Bob", 24);

person1.greet(); // Output: Hi, I'm Alice and I'm 30 years old.


person2.greet(); // Output: Hi, I'm Bob and I'm 24 years old.

4. ES6 Classes (Syntactic Sugar for Constructor Functions):


Introduced in ES6, classes provide a cleaner, more object-oriented syntax for creating constructor functions
and managing prototypes.

class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
}
makeSound() {
console.log(`${this.name} makes a sound.`);
}
}

const cat = new Animal("Whiskers", "Cat");


cat.makeSound(); // Output: Whiskers 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.`);
}
};

const dog = Object.create(animalPrototype);


dog.name = "Max";
dog.makeSound(); // Output: Max makes a sound.

Accessing Object Properties:


You can access properties using two main notations:

1. Dot Notation (preferred for known property names):

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):

console.log(user["lastName"]); // Output: Doe

const propertyName = "email";


console.log(user[propertyName]); // Output: john.doe@example.com

const anotherObject = { "my-key with space": "hello" };


console.log(anotherObject["my-key with space"]); // Output: hello

Iterating Over Object Properties:


for...in loop: Iterates over the enumerable string properties of an object, including inherited ones. (Often used,
but be aware of inherited properties.)

for (let key in user) {


console.log(`${key}: ${user[key]}`);
}
// Output:
// firstName: John
// lastName: Doe
// age: 25
// email: john.doe@example.com

Object.keys() : Returns an array of a given object's own enumerable property names (keys).

const keys = Object.keys(user);


console.log(keys); // [ 'firstName', 'lastName', 'age', 'email' ]
keys.forEach(key => {
console.log(`${key}: ${user[key]}`);
});

Object.values() : Returns an array of a given object's own enumerable property values.

const values = Object.values(user);


console.log(values); // [ 'John', 'Doe', 25, 'john.doe@example.com' ]

Object.entries() : Returns an array of a given object's own enumerable string-keyed property [key, value] pairs.

const entries = Object.entries(user);


console.log(entries);
// Output:
// [
// [ 'firstName', 'John' ],
// [ 'lastName', 'Doe' ],
// [ 'age', 25 ],
// [ 'email', 'john.doe@example.com' ]
// ]

for (const [key, value] of entries) {


console.log(`${key}: ${value}`);
}

Important Built-in Object Methods (Static Methods of Object ):

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.keys() , Object.values() , Object.entries() : As described above, for iterating.

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:

Key Characteristics of JavaScript Arrays:


1. Ordered Collection: Elements are stored in a specific order, and that order is maintained.

2. Zero-Indexed: The first element is at index 0 , the second at 1 , and so on.

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:

1. Array Literal (Most Common and Recommended):


This is the simplest and most widely used method.

const numbers = [1, 2, 3, 4, 5];


const fruits = ["apple", "banana", "cherry"];
const mixed = [1, "hello", true, { id: 1 }, [6, 7]];
const emptyArray = [];

2. Array() Constructor:
Less common for simple arrays, but useful for creating an array of a specific size with empty slots.

const arr1 = new Array(); // Creates an empty array


const arr2 = new Array(3); // Creates an array with 3 empty slots [ <3 empty items> ]
const arr3 = new Array(1, 2, 3); // Creates an array with elements 1, 2, 3 (same as [1, 2, 3])

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.

Accessing Array Elements:


Elements are accessed using bracket notation [] with their index.

const colors = ["red", "green", "blue"];

console.log(colors[0]); // Output: "red" (first element)


console.log(colors[1]); // Output: "green"
console.log(colors[2]); // Output: "blue"
console.log(colors[3]); // Output: undefined (index out of bounds)

Modifying Array Elements:


Elements can be modified by assigning a new value to a specific index.

const scores = [85, 92, 78];


scores[1] = 95; // Change the second element
console.log(scores); // Output: [85, 95, 78]

Array length Property:


The length property returns the number of elements in the array.

const items = ["a", "b", "c", "d"];


console.log(items.length); // Output: 4

// You can also change the length of an array.


// If you set a smaller length, elements are truncated.
items.length = 2;
console.log(items); // Output: ["a", "b"]

// If you set a larger length, empty slots are added.


items.length = 5;
console.log(items); // Output: ["a", "b", <3 empty items>]

Common Array Methods (Crucial for Array Manipulation):


JavaScript provides a rich set of built-in methods for array manipulation. Here are some of the most frequently
used:

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

const arr = [1, 2];


arr.push(3, 4);
console.log(arr); // [1, 2, 3, 4]

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

const arr = [1, 2, 3];


const lastElement = arr.pop();
console.log(arr); // [1, 2]
console.log(lastElement); // 3

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.

const arr = [1, 2, 3];


const firstElement = arr.shift();
console.log(arr); // [2, 3]
console.log(firstElement); // 1

splice(startIndex, deleteCount, ...itemsToAdd) : A powerful method for adding, removing, or replacing elements at any
position.

const arr = ["a", "b", "c", "d"];


arr.splice(1, 2, "x", "y"); // At index 1, delete 2 elements, then add "x", "y"
console.log(arr); // ["a", "x", "y", "d"]

arr.splice(1, 0, "new"); // At index 1, delete 0 elements, then add "new"


console.log(arr); // ["a", "new", "x", "y", "d"]

arr.splice(2, 1); // At index 2, delete 1 element


console.log(arr); // ["a", "new", "y", "d"]

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.

Iterating Over Arrays:


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

const numbers = [1, 2, 3];


numbers.forEach(function(number, index) {
console.log(`Index ${index}: ${number}`);
});
// Output:
// Index 0: 1
// Index 1: 2
// Index 2: 3

map(callback) : Creates a new array with the results of calling a provided function on every element in the calling
array.

const numbers = [1, 2, 3];


const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]

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.

const numbers = [1, 2, 3, 4];


const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 10 (0 + 1 + 2 + 3 + 4)

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.

const arr = [1, 2, 3, 4, 5];


const sliced = arr.slice(1, 4); // Elements from index 1 up to (but not including) 4
console.log(sliced); // [2, 3, 4]
console.log(arr); // [1, 2, 3, 4, 5] (original unchanged)

concat(...arrays) : Used to merge two or more arrays. This method does not change the existing arrays, but
instead returns a new array.

const arr1 = [1, 2];


const arr2 = [3, 4];
const combined = arr1.concat(arr2, [5, 6]);
console.log(combined); // [1, 2, 3, 4, 5, 6]

(Note: The spread syntax [...arr1, ...arr2] is often preferred for combining arrays in modern JS.)

join(separator) : Joins all elements of an array into a string.

const words = ["Hello", "World", "!"];


const sentence = words.join(" ");
console.log(sentence); // "Hello World !"

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

sort(compareFunction) : Sorts the elements of an array in place. For numbers, you often need a compareFunction .

const nums = [3, 1, 4, 1, 5, 9];


nums.sort((a, b) => a - b); // Ascending sort for numbers
console.log(nums); // [1, 1, 3, 4, 5, 9]

const names = ["Charlie", "Alice", "Bob"];


names.sort(); // Lexicographical (string) sort
console.log(names); // ["Alice", "Bob", "Charlie"]

Iteration with for...of Loop (ES6+):


The for...of loop is a concise and readable way to iterate over the values of an iterable (like an array).

const fruits = ["apple", "banana", "cherry"];


for (const fruit of fruits) {
console.log(fruit);
}
// Output:
// apple

Javascript Notes 44
// banana
// cherry

11. DOM(Document Object Model)


The Document Object Model (DOM) is a programming interface for web documents (HTML, XML, and SVG). It
represents the page so that programs can change the document structure, style, and content. Essentially, it's a
tree-like representation of your web page, where each part of the document (elements, attributes, text) is
represented as a node.
Think of it this way: when your web browser loads an HTML page, it doesn't just display the raw text. It parses the
HTML and creates a structured, in-memory representation of that page. This representation is the DOM.
JavaScript then interacts with this DOM to make web pages dynamic and interactive.

Key Aspects of the DOM:


1. Tree Structure (DOM Tree):HTML

The DOM organizes all the elements of an HTML document in a hierarchical tree structure.

The document object is the root node of the entire tree.

<html> is a child of the document .

<head> and <body> are children of <html> .

Elements inside <body> are children of <body> , and so on.

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>

Simplified DOM Tree Representation:

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:

Document Node: The root of the tree ( document object).

Javascript Notes 45
Element Node: Represents an HTML tag (e.g., <h1> , <p> , <div> ).

Text Node: Represents the actual text content within elements.

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.

Comment Node: Represents HTML comments (``).

3. API (Application Programming Interface):


The DOM provides a set of objects, properties, and methods that JavaScript can use to interact with the HTML
document. This API allows you to:

Find/Select Elements: Locate specific elements on the page.

Change Content: Modify the text or HTML inside elements.

Change Styles: Alter the CSS properties of elements.

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.).

Common DOM Manipulation with JavaScript:


The document object is your entry point to the DOM.

1. Selecting Elements:
document.getElementById('idName') : Selects a single element by its unique id attribute. (Most efficient for single
elements.)

const heading = document.getElementById('main-heading');


console.log(heading.textContent); // Output: Welcome!

document.getElementsByClassName('className') : Returns an HTMLCollection (live, array-like object) of all elements with a


given class name.

const introParagraphs = document.getElementsByClassName('intro');


console.log(introParagraphs[0].textContent); // Output: This is a paragraph.

document.getElementsByTagName('tagName') : Returns an HTMLCollection of all elements with a given tag name.

const allParagraphs = document.getElementsByTagName('p');


console.log(allParagraphs.length); // Output: 1

document.querySelector('CSS-selector') (ES5+): Returns the first element that matches the specified CSS selector.

const firstParagraph = document.querySelector('.intro');


const firstButton = document.querySelector('button');
console.log(firstParagraph.textContent); // Output: This is a paragraph.

document.querySelectorAll('CSS-selector') (ES5+): Returns a NodeList (static, array-like object) of all elements that match
the specified CSS selector.

const allElements = document.querySelectorAll('p, h1');


allElements.forEach(el => console.log(el.tagName)); // Output: H1, P

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

XSS vulnerabilities if dealing with untrusted input.

const div = document.createElement('div');


div.innerHTML = "<b>Important</b> message.";
document.body.appendChild(div);
// Adds a div with bold text to the body

3. Modifying Attributes:
element.attributeName : Direct property access for common attributes.

const myImage = document.querySelector('img'); // Assuming an <img> tag exists


myImage.src = "new-image.jpg";
myImage.alt = "A new image";

element.setAttribute('attributeName', 'value') : Sets the value of any attribute.

element.getAttribute('attributeName') : Gets the value of any attribute.

element.removeAttribute('attributeName') : Removes an attribute.

const link = document.querySelector('a'); // Assuming an <a> tag


link.setAttribute('href', 'https://example.com');
console.log(link.getAttribute('target')); // null if not set, or "_blank" etc.

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.

element.classList.add('className') : Adds a CSS class.

element.classList.remove('className') : Removes a CSS class.

element.classList.toggle('className') : Toggles a CSS class (adds if not present, removes if present).

element.classList.contains('className') : Checks if a class exists.

const myDiv = document.getElementById('myDiv');


myDiv.classList.add('active'); // Add a class
if (myDiv.classList.contains('highlight')) {
myDiv.classList.remove('highlight');
}

5. Creating and Appending Elements:


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

document.createTextNode('text') : Creates a new text node.

parentElement.appendChild(childElement) : Appends a child node to the end of a parent's children.

parentElement.insertBefore(newNode, referenceNode) : Inserts a node before a reference node.

const newParagraph = document.createElement('p');


const textNode = document.createTextNode('This is a dynamically created paragraph.');

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.

parentElement.removeChild(childElement) (Older way): Removes a specified child node from a parent.

const oldParagraph = document.querySelector('.intro');


if (oldParagraph) {
oldParagraph.remove(); // Removes the paragraph from the document
}

7. Event Handling:
The DOM allows you to attach event listeners to elements to react to user interactions.

element.addEventListener('eventName', callbackFunction) : The standard and recommended way.

const myButton = document.querySelector('button');


myButton.addEventListener('click', function() {
alert('Button clicked!');
});

element.onclick = function() { ... } (Older/Simpler): Direct event handler property. Limited to one handler per event
type.

Why is the DOM important?


The DOM is the bridge between your static HTML/XML document and dynamic JavaScript code. It's what allows
JavaScript to:

Build interactive user interfaces.

Respond to user actions in real-time.

Fetch data from servers and update parts of the page without a full reload (AJAX).

Create animations and visual effects.

Build complex web applications

12. ES6+ Features


ES6 (ECMAScript 2015) marked a significant overhaul of JavaScript, introducing a plethora of new features and
syntax enhancements that fundamentally changed how we write modern JavaScript. Subsequent annual releases
(ES2016, ES2017, etc., collectively referred to as ES6+) have continued to add powerful capabilities.
Here's a breakdown of the most important ES6+ features you'll encounter in modern JavaScript development:

1. let and const (Block-Scoped Declarations)


Before ES6, var was the only way to declare variables, and it had function-level scope and hoisting quirks. let and
const provide more predictable and safer variable declarations:

let : Declares a block-scoped variable that can be reassigned.

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.

const person = { name: "Alice" };


person.name = "Bob"; // Allowed (modifying content, not reassigning the object itself)
// person = { name: "Charlie" }; // TypeError: Assignment to constant variable.

Benefit: Reduces bugs related to variable hoisting and unintended reassignments, leading to more robust code.

2. Arrow Functions ( => )


A more concise syntax for writing function expressions, with a crucial difference in this binding.

// Traditional function expression


const add = function(a, b) {
return a + b;
};

// Arrow function (concise syntax for single expression)


const subtract = (a, b) => a - b;

// Arrow function with single parameter (parentheses optional)


const square = num => num * num;

// Arrow function with no parameters


const greet = () => "Hello!";

// Arrow function with block body (requires explicit return)


const complexOperation = (x, y) => {
let result = x * y + 10;
return result;
};

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.

3. Template Literals (Backticks ` )


Enhance string manipulation by allowing embedded expressions and multi-line strings.

const name = "John";


const age = 30;

// Before ES6
const messageOld = "My name is " + name + " and I am " + age + " years old.";

// With Template Literals


const messageNew = `My name is ${name} and I am ${age} years old.`;
console.log(messageNew); // Output: My name is John and I am 30 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:

const colors = ["red", "green", "blue"];


const [firstColor, secondColor, thirdColor] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

// Skip elements
const [, , lastColor] = colors;
console.log(lastColor); // "blue"

Object Destructuring:

const person = { firstName: "Jane", lastName: "Doe", job: "Developer" };


const { firstName, job } = person;
console.log(firstName); // "Jane"
console.log(job); // "Developer"

// Assign to new variable names


const { firstName: fName, lastName: lName } = person;
console.log(fName); // "Jane"

// Default values
const { city = "Unknown" } = person;
console.log(city); // "Unknown"

Benefit: Cleaner code for extracting data from complex objects and arrays.

5. Spread ( ... ) and Rest ( ... ) Operators


Both use the ... syntax but have different functionalities based on context.

Spread Operator ( ... ): Expands an iterable (like an array or string) or an object into individual elements.

Array concatenation/copying:

const arr1 = [1, 2];


const arr2 = [3, 4];
const combined = [...arr1, ...arr2, 5]; // [1, 2, 3, 4, 5]
const copiedArr = [...arr1]; // Creates a shallow copy

Object merging/copying (ES2018+):

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

Passing arguments to functions:

function sum(a, b, c) { return a + b + c; }


const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

Javascript Notes 50
Rest Parameter ( ... ): Gathers an indefinite number of arguments into an array.

function logArguments(first, second, ...rest) {


console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5] (an actual array)
}
logArguments(1, 2, 3, 4, 5);

Benefit: Simplifies array/object manipulation, function arguments, and makes code more readable.

6. Classes (Syntactic Sugar for Prototypes)


Introduced a more familiar object-oriented syntax for creating constructor functions and managing inheritance.
Under the hood, JavaScript still uses prototype-based inheritance.

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

greet() { // Method
return `Hello, my name is ${this.name}.`;
}

static species = "Homo Sapiens"; // Static property (ES2022+)


static info() { // Static method
console.log("This is a Person class.");
}
}

const john = new Person("John Doe", 30);


console.log(john.greet()); // "Hello, my name is John Doe."
console.log(Person.species); // "Homo Sapiens"

class Student extends Person {


constructor(name, age, studentId) {
super(name, age); // Call parent constructor
this.studentId = studentId;
}

study() {
console.log(`${this.name} is studying.`);
}
}

const alice = new Student("Alice Smith", 20, "S123");


alice.greet(); // "Hello, my name is Alice Smith."
alice.study(); // "Alice Smith is studying."

Benefit: Provides a clearer, more structured way to create objects and implement inheritance, aligning with class-
based languages.

7. Modules ( import and export )


Native support for organizing JavaScript code into separate files (modules), allowing for better code organization,
reusability, and dependency management.
math.js :

Javascript Notes 51
export const PI = 3.14159;

export function add(a, b) {


return a + b;
}

export default function multiply(a, b) { // Only one default export per module
return a * b;
}

app.js :

import { PI, add } from './math.js'; // Named imports


import multiplyNumbers from './math.js'; // Default import (can be named anything)

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.

function greet(name = "Guest", greeting = "Hello") {


return `${greeting}, ${name}!`;
}

console.log(greet("Alice")); // "Hello, Alice!"


console.log(greet("Bob", "Hi")); // "Hi, Bob!"
console.log(greet()); // "Hello, Guest!"
console.log(greet(undefined, "Hey")); // "Hey, Guest!"

Benefit: Makes functions more robust and reduces the need for manual checks for missing arguments.

9. Promises (for Asynchronous Operations)


A fundamental change for handling asynchronous code (like fetching data from an API). Promises provide a
cleaner, more manageable alternative to deeply nested callbacks ("callback hell").

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.");
});

Benefit: Improves readability and error handling for asynchronous code.

10. Async/Await (ES2017+)


Syntactic sugar built on top of Promises, making asynchronous code look and behave more like synchronous
code, further improving readability.

async function getData() {


try {
console.log("Fetching data...");
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1"); // Await the promise resolution
const data = await response.json(); // Await the promise resolution
console.log(data);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
console.log("Data fetch process finished.");
}
}

getData();

Benefit: Makes asynchronous code even easier to read, write, and debug than raw Promises.

Other Notable ES6+ Features:


for...of loop: Iterates directly over the values of iterable objects (arrays, strings, Maps, Sets).

Map and Set : New built-in data structures for key-value pairs (Map) and unique values (Set).

Enhanced Object Literals: Shorthand property names, method properties.

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).

13. Asynchronous Javascript


Asynchronous programming is a cornerstone of modern JavaScript, especially in environments like web browsers
and Node.js. It's crucial for building responsive and efficient applications. To understand asynchronous
JavaScript, it's important to first grasp the concept of synchronous execution.

Synchronous JavaScript (Blocking)


In synchronous programming, code is executed sequentially, one line at a time, in the order it appears. Each
operation must complete before the next one can start. If a particular operation takes a long time (e.g., fetching
data from a server, reading a large file, or performing a complex calculation), the entire program will "block" or
"freeze" until that operation finishes. This leads to a poor user experience, as the UI might become unresponsive.
Example (Synchronous):

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;
}

const result = heavyCalculation(); // This line will block execution


console.log("Calculation done:", result);

console.log("End");

In a browser, running heavyCalculation() would make the page unresponsive until it finishes.

Asynchronous JavaScript (Non-Blocking)


Asynchronous programming allows certain operations to be executed concurrently or in the background without
blocking the main execution thread. When an asynchronous operation is started, the program doesn't wait for it to
complete; it moves on to the next line of code. Once the asynchronous operation finishes, it notifies the program,
and a pre-defined "callback" function or handler is executed with the result.
This is essential for operations that involve waiting for external resources, such as:

Network requests (AJAX, Fetch API): Getting data from APIs.

Timers: setTimeout , setInterval .

File I/O: Reading/writing files (in Node.js).

Database operations.

User interactions: Clicks, keypresses, etc. (event handling).

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.

Evolution of Asynchronous Patterns in JavaScript:


Historically, asynchronous JavaScript was managed primarily with callbacks. However, modern JavaScript (ES6+)
introduced more powerful and readable patterns:

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.

// Callback Hell example


getData(function(a) {
processData(a, function(b) {
saveData(b, function(c) {
console.log("All done:", c);
});
});
});

2. Promises (ES6 / ECMAScript 2015):JavaScript


Promises provide a cleaner and more structured way to handle asynchronous operations, especially when
chaining multiple operations. A Promise represents the eventual completion (or failure) of an asynchronous
operation and its resulting value.
A Promise can be in one of three states:

pending : Initial state, neither fulfilled nor rejected.

fulfilled : Meaning that the operation completed successfully.

rejected : Meaning that the operation failed.

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.

An async function always returns a Promise.

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);
});
}

async function processData() {


try {
console.log("Fetching data...");
const data = await fetchDataAsync(); // Pause here until Promise resolves
console.log(data);

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 ).

Why Asynchronous JavaScript is Crucial:


User Experience: Keeps the UI responsive, preventing the "frozen" browser tab or application. Users can
continue interacting with the page while background tasks are running.

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!");
}
};

console.log(car.make); // Accessing property


car.start(); // Calling method

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
}
}

const myAccount = new BankAccount(100);


myAccount.deposit(50);
myAccount.withdraw(30);
console.log(myAccount.getBalance()); // 120
// console.log(myAccount.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing

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.

// Using ES6 Classes for inheritance (syntactic sugar over prototypes)


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

eat() {
console.log(`${this.name} is eating.`);
}
}

class Dog extends Animal { // Dog inherits from Animal


constructor(name, breed) {
super(name); // Call the parent class's constructor
this.breed = breed;
}

bark() {
console.log("Woof woof!");
}

// Override parent method


eat() {
console.log(`${this.name} (a ${this.breed}) is happily munching.`);
}
}

const myDog = new Dog("Buddy", "Golden Retriever");


myDog.eat(); // Buddy (a Golden Retriever) is happily munching. (overridden method)
myDog.bark(); // Woof woof!
console.log(myDog.name); // Buddy
console.log(myDog.breed); // Golden Retriever

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()

method, but their implementations are different.

const genericAnimal = new Animal("Lion");


genericAnimal.eat(); // Lion is eating.

const specificDog = new Dog("Max", "German Shepherd");


specificDog.eat(); // Max (a German Shepherd) is happily munching.

function makeThemEat(creature) {
creature.eat(); // Calls the appropriate eat() method based on the object's type
}

makeThemEat(genericAnimal); // Lion is eating.


makeThemEat(specificDog); // Max (a German Shepherd) is happily munching.

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.

How JavaScript Achieves OOP: Prototypes


Understanding prototypes is key to understanding OOP in JavaScript, especially before ES6 classes became
widely adopted.
Every JavaScript object has a private property [[Prototype]] (exposed as __proto__ in many environments, though it's
not standard for direct access) which points to another object, its prototype. When you try to access a property
or method on an object, if it's not found on the object itself, JavaScript looks up the prototype chain until it finds
the property or reaches the end of the chain ( null ).
Example (Pre-ES6 / Prototypal Inheritance):

// Constructor function (blueprint for creating objects)


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

// Methods are added to the prototype to be shared by all instances


OldPerson.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}.`);
};

const personA = new OldPerson("Alice");


const personB = new OldPerson("Bob");

personA.greet(); // Hello, my name is Alice.


personB.greet(); // Hello, my name is Bob.

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.

Why OOP in JavaScript?


Code Organization: Structures complex applications into logical, reusable components.

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.

Scalability: Facilitates building larger, more complex applications.

15. Error Handling in Js

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.

JavaScript provides several mechanisms for handling errors:

1. try...catch...finally Statement
This is the primary way to handle synchronous errors in JavaScript.

try block: Contains the code that might throw an error.

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

let result2 = divide(10, 0); // This will throw an error


console.log("Result 2:", result2); // This line will not be executed
} catch (error) {
console.error("An error occurred:", error.message); // Output: An error occurred: Cannot divide by zero.
} finally {
console.log("Division attempt finished."); // Always executes
}

console.log("Program continues after error handling.");

Using finally for cleanup:

function readFile(filePath) {
let fileHandle; // This would typically be a resource handle

try {
// Simulate opening a file
console.log(`Opening file: ${filePath}`);
fileHandle = "someFileResource";

if (Math.random() < 0.5) { // Simulate a random error during read


throw new Error("Error reading file content.");
}

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

2. The Error Object


When an error occurs, JavaScript creates an Error object (or an instance of a more specific error type). Key
properties of the Error object include:

name : The type of the error (e.g., 'Error' , 'TypeError' , 'ReferenceError' , 'RangeError' , 'SyntaxError' ).

message : A human-readable description of the error.

: (Non-standard, but widely supported) A string representing the call stack at the moment the error was
stack

created. Useful for debugging.

Common built-in Error Types:

ReferenceError : Trying to use a variable that hasn't been declared.

try {
console.log(undeclaredVar);
} catch (e) {
console.error(e.name + ": " + e.message); // ReferenceError: undeclaredVar is not defined
}

TypeError : An operation is performed on a value that is not of the expected type.

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)

RangeError : A number is outside its acceptable range.

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).

3. Custom Error Types


You can create your own custom error types by extending the built-in Error class. This allows for more specific
error handling and better code organization.

class CustomAPIError extends Error {


constructor(message, statusCode) {
super(message); // Call the parent Error constructor
this.name = "CustomAPIError"; // Set a specific name
this.statusCode = statusCode || 500;
}
}

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);
}
}

4. Asynchronous Error Handling


The try...catch block does not catch errors in asynchronous code directly unless the asynchronous operation itself
is wrapped in a Promise that is await ed, or if the try...catch is within the asynchronous callback itself.

a) Promises and .catch()


When working with Promises, the .catch() method is designed for handling errors that occur during the Promise
chain.

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.");
});

b) async/await with try...catch (Recommended)


This is the most modern and readable way to handle asynchronous errors, making them appear synchronous.

async function getData() {


try {
console.log("Attempting to fetch...");
// await can throw an error if the promise it waits for rejects
const response = await fetch("https://invalid-url-for-test.com/data");
if (!response.ok) { // Check for HTTP errors (e.g., 404, 500)
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log("Data received:", data);
} catch (error) { // Catches network errors or errors thrown in the try block
console.error("Failed to get data:", error.message);
} finally {
console.log("Async data retrieval process finished.");
}
}

getData();

5. Global Error Handling ( window.onerror / process.on('uncaughtException') )


Browser ( window.onerror ): Catches uncaught errors that bubble up to the global scope. This is useful for logging
client-side errors.

window.onerror = function(message, source, lineno, colno, error) {


console.error("Global Error Caught:");
console.error("Message:", message);
console.error("Source:", source);
console.error("Line:", lineno);
console.error("Column:", colno);
console.error("Error Object:", error);
return true; // Return true to prevent the default browser error handling (e.g., console log)

Javascript Notes 63
};

// This error will be caught by window.onerror


// undefinedVariable.method();

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.

process.on('uncaughtException', (err) => {


console.error('Caught uncaught exception:', err);
// Perform cleanup and exit gracefully
process.exit(1); // Exit with a non-zero code to indicate an error
});

// This will trigger the uncaughtException handler


// setTimeout(() => {
// throw new Error('This is an uncaught error!');
// }, 1000);

Best Practices for Error Handling:


Fail Fast, Fail Loudly (During Development): Don't silently suppress errors during development. Let them
break your code so you can identify and fix them.

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.

16. Tooling and workflow in js


JavaScript tooling and workflow have evolved tremendously over the years, making development more efficient,
reliable, and enjoyable. Modern JavaScript development is rarely just writing .js files and opening them in a
browser. It involves a sophisticated ecosystem of tools that automate tasks, improve code quality, and optimize
performance.
Here's a breakdown of essential tooling and workflow concepts in modern JavaScript:

I. Core Development Tools


1. Node.js & npm/Yarn/pnpm:

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 .

2. Code Editors / IDEs:

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.

II. Code Quality & Consistency


1. Linters (ESLint, JSHint):

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.

III. Build Tools & Transpilers


1. Transpilers (Babel):

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.

2. Module Bundlers (Webpack, Rollup, Parcel, Vite):

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).

Parcel: A "zero-configuration" bundler, great for quick setups.

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.

3. Task Runners (Deprecated - npm scripts, Webpack scripts, Gulp/Grunt):

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.

Vitest: A new, fast testing framework powered by Vite.

Workflow: Write test files ( .test.js or .spec.js ), run the test runner from the command line ( npm test ).

2. Assertion Libraries (Chai, Jest's Built-in):

Purpose: Provide functions to assert that certain conditions are met during tests (e.g.,
expect(result).toBe(expected) ).

3. Mocking Libraries (Jest's Built-in, Sinon.js):

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.

VI. Other Important Tools & Concepts


1. Package.json:

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.

3. Dev Servers (Webpack Dev Server, Vite Dev Server):

Purpose: Provide a local server for development with features like hot module replacement (HMR), live
reloading, and proxying API requests.

Workflow: Started via an npm script during development.

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).

5. CI/CD (Continuous Integration/Continuous Deployment):

Purpose: Automate the build, test, and deployment process.

Tools: GitHub Actions, GitLab CI/CD, Jenkins, Travis CI.

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:

Create a new project directory.

npm init (or yarn init , pnpm init ).

Install core dependencies (frameworks, libraries).

Install development dependencies (Babel, Webpack/Vite, ESLint, Prettier, Jest).

2. Development Loop:

Write code in modern JavaScript (ES6+, TypeScript, JSX/TSX).

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.

Use browser DevTools for interactive debugging.

3. Testing:

Write unit, integration, and end-to-end tests.

Run tests regularly ( npm test ).

4. Version Control:

Stage and commit changes ( git add , git commit ).

Push to remote repository ( git push ).

5. Build & Deployment:

Run npm run build to create optimized, transpiled, and bundled production assets.

Deploy the built assets to a web server or hosting platform.

(Optional) CI/CD pipeline automates steps 3-5.

17. Working with APIs in Js


Working with APIs (Application Programming Interfaces) in JavaScript is a cornerstone of modern web
development. APIs allow your JavaScript code to communicate with external services, retrieve data, send data,
and perform actions on remote servers. This is how dynamic web applications fetch news, weather, user profiles,
product data, or interact with backend services.

In web browsers, the primary mechanism for interacting with APIs is through HTTP requests.

Key Concepts for Working with APIs in JavaScript


1. HTTP (Hypertext Transfer Protocol):

The foundation of data communication on the web.

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).

2. JSON (JavaScript Object Notation):

The most common data format for exchanging data between a web browser and a server.

It's lightweight, human-readable, and easily parseable by JavaScript.

JSON.parse() : Converts a JSON string into a JavaScript object.

JSON.stringify() : Converts a JavaScript object into a JSON string.

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.

Methods for Making API Requests in JavaScript


Historically, XMLHttpRequest (XHR) was used, but modern JavaScript primarily uses Fetch API and Axios (a popular
library).

1. The Fetch API (Modern & Built-in)


The Fetch API is a modern, Promise-based API for making network requests, available natively in all modern
browsers and Node.js (via polyfill or native support from v18+).
a) GET Request (Retrieving Data)

async function fetchPosts() {


try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts'); // Default method is GET

// Check if the request was successful (HTTP status 200-299)


if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const posts = await response.json(); // Parse the JSON response body


console.log('Fetched Posts:', posts.slice(0, 3)); // Log first 3 posts

} catch (error) {
console.error('Error fetching posts:', error);
// You might update the UI to show an error message
}
}

fetchPosts();

b) POST Request (Sending Data)

async function createPost() {


const newPost = {
title: 'My New Post',
body: 'This is the content of my new post.',
userId: 1,
};

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();

c) PUT / PATCH / DELETE Requests (Similar Structure)

async function updatePost(id) {


const updatedData = {
title: 'Updated Post Title',
};

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}`);
}

const result = await response.json();


console.log('Updated Post:', result);

} catch (error) {
console.error('Error updating post:', error);
}
}

// updatePost(1); // Call to update post with ID 1

async function deletePost(id) {


try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'DELETE',
});

// For DELETE, 200 OK or 204 No Content are common success codes


if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

console.log(`Post with ID ${id} deleted successfully (status: ${response.status})`);


// Note: delete operations often return an empty body or just a status, so .json() might fail.
// await response.json(); // Might throw error if no body

} catch (error) {
console.error('Error deleting post:', error);
}
}

// deletePost(1); // Call to delete post with ID 1

2. Axios (Popular Third-Party Library)

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

import axios from 'axios'; // Or require('axios') in CommonJS

async function fetchUsersAxios() {


try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
console.log('Fetched Users (Axios):', response.data.slice(0, 2)); // Axios wraps response in 'data'

} 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();

async function createUserAxios() {


const newUser = {
name: 'Jane Doe',
email: 'jane.doe@example.com',
};

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();

Best Practices for API Work


Handle Asynchronicity: Always use async/await (with try...catch ) or .then().catch() with Promises.

Error Handling:

Always check response.ok (for Fetch) or rely on catch block (for Axios) to detect non-2xx HTTP status codes.

Provide meaningful error messages to the user.

Log detailed error information for debugging.

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.

// Example of a simple API service abstraction


const apiService = {
getPosts: async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) throw new Error('Failed to fetch posts');
return response.json();
},
createPost: async (postData) => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postData),
});
if (!response.ok) throw new Error('Failed to create post');
return response.json();
}
// ... more methods for other API endpoints
};

async function loadAndDisplayPosts() {


console.log("Loading posts...");
try {
const posts = await apiService.getPosts();
console.log("Displaying posts:", posts.slice(0, 2));
// Update DOM here
} catch (error) {
console.error("Error loading posts:", error.message);
// Display error message to user
} finally {
console.log("Finished loading posts.");
}
}

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

You might also like