[go: up one dir, main page]

0% found this document useful (0 votes)
20 views171 pages

Mastering JavaScript From Beginner To Advanced

Livro guia sobre o aprendizado de javascript. Ele guia na aprendizagem da linguagem do nivel iniciante ao avançado.

Uploaded by

anaclaradurasio
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)
20 views171 pages

Mastering JavaScript From Beginner To Advanced

Livro guia sobre o aprendizado de javascript. Ele guia na aprendizagem da linguagem do nivel iniciante ao avançado.

Uploaded by

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

Mastering JavaScript: From Beginner to

Advanced
Welcome, Aspiring JavaScript Developer!
JavaScript is the backbone of modern web development—powerful, dynamic, and essential for
creating interactive web applications. Whether you are a complete beginner or an experienced
developer looking to refine your skills, this book will serve as a comprehensive guide to mastering
JavaScript from the ground up.
My name is Dhanian, and I am a software developer and tutor dedicated to making programming
education accessible and effective. With years of experience in both coding and teaching, I have
crafted this book to provide a structured, hands-on approach to learning JavaScript.

Why This Book?


This book is designed to be your complete JavaScript learning resource, covering:
• Fundamentals for Beginners – A clear and structured introduction to JavaScript, ensuring
you build a solid foundation.
• Intermediate and Advanced Topics – Dive deep into essential JavaScript concepts,
including ES6+ features, asynchronous programming, and object-oriented techniques.
• Hands-on Learning – Real-world examples, exercises, and projects to reinforce concepts
and enhance your practical skills.
• Modern JavaScript Practices – Stay up to date with the latest best practices and industry
trends.
By the end of this book, you will have the knowledge and confidence to build dynamic web
applications, work with APIs, and even transition into JavaScript frameworks like React.

Support My Work
Creating high-quality educational content takes dedication and effort. If you find this book valuable
and would like to support my work, consider contributing at ko-fi.com/codewithdhanian. Your
support helps me continue providing resources to empower developers worldwide.

Copyright Notice
© 2025 Dhanian. All rights reserved. No part of this publication may be copied, stored, or
transmitted in any form, whether electronic, mechanical, or otherwise, without the prior written
permission of the author, except in the case of brief quotations for review or educational purposes.
Comprehensive Table of Contents
Mastering JavaScript: From Beginner to Advanced
This ebook is designed to be more than just a learning resource—it’s a complete JavaScript
mastery guide featuring case studies, quizzes, coding exercises, and real-world project
challenges to reinforce your understanding.

PART 1: INTRODUCTION TO JAVASCRIPT


Chapter 1: Getting Started with JavaScript
1.1 Understanding JavaScript
• What is JavaScript and Why is it Essential?
• How JavaScript Powers the Web
• JavaScript Evolution: ES5, ES6, and ESNext
• JavaScript vs. Java vs. TypeScript

1.2 Setting Up Your Development Environment


• Choosing the Right Code Editor (VS Code, Sublime, Atom)
• Configuring VS Code for JavaScript Development
• Browser Developer Tools: Console, Debugger, and Network Tab
• Structuring a JavaScript Project: Best Practices

1.3 Writing and Running JavaScript Code


• Writing Your First JavaScript Program (Hello, World!)
• Executing JavaScript in the Browser Console
• Running JavaScript in Node.js

📌
• Frontend vs. Backend JavaScript: Key Differences

📝
💻
Case Study: How Google Chrome’s V8 Engine Executes JavaScript
Quiz: JavaScript Basics
Coding Challenge: Create a program that prints a custom greeting based on user input.

Chapter 2: JavaScript Fundamentals


2.1 JavaScript Syntax and Best Practices
• Understanding Code Structure
• Writing Clean and Readable Code
• Using Comments for Documentation

2.2 Variables and Data Types


• Declaring Variables with var, let, and const
• JavaScript Data Types: String, Number, Boolean, Object, Array
• Checking Data Types with typeof
• Using Template Literals (${})

2.3 Operators and Expressions


• Arithmetic, Logical, and Comparison Operators

📌
• Assignment Operators and Their Applications

📝
💻
Case Study: How JavaScript Handles Type Coercion
Quiz: Variables and Data Types
Coding Challenge: Swap two variables without using a temporary variable.

Chapter 3: Control Flow and Loops


3.1 Conditional Statements
• if, else if, and else Statements
• The Ternary Operator (condition ? value1 : value2)
• Using switch Statements

3.2 Loops and Iterations


• for, for...in, and for...of Loops
• while and do...while Loops

📌
• Controlling Loops with break and continue

📝
💻
Case Study: How Social Media Feeds Use Loops to Load More Content
Quiz: Loops and Conditionals
Project Challenge: Build a number-guessing game using loops and conditionals.

PART 2: INTERMEDIATE JAVASCRIPT


Chapter 4: Functions and Execution Context
4.1 Understanding Functions
• Function Declarations vs. Function Expressions
• Arrow Functions (=>) and Their Benefits
• Default Function Parameters

4.2 Function Scope and Closures


• Scope Chain and Nested Functions

📌
• Understanding Closures and Their Use Cases

📝
💻
Case Study: How JavaScript Closures Work in Event Listeners
Quiz: Functions and Scope
Coding Challenge: Implement a closure-based function that generates unique IDs.

Chapter 5: Working with Objects and Arrays


5.1 JavaScript Objects
• Creating and Accessing Object Properties
• Using Methods and the this Keyword
• Object Destructuring and Spread Operator

5.2 Arrays in JavaScript


• Array Properties and Methods
• Iterating Over Arrays (forEach, map, filter, reduce)

📌
• Sorting and Searching (sort, find, some, every)

📝
💻
Case Study: How E-commerce Sites Use Arrays for Shopping Carts
Quiz: Objects and Arrays
Project Challenge: Build a product inventory system using objects and arrays.

PART 3: DOM MANIPULATION AND


EVENTS
Chapter 6: Understanding the DOM
6.1 Introduction to the Document Object Model
• The DOM Tree and Node Types
• How Browsers Interpret HTML and CSS

6.2 Selecting and Manipulating Elements


• getElementById, querySelector, and querySelectorAll
• Changing HTML and CSS with JavaScript
📌
📝 Case Study: How JavaScript Powers Interactive Web Pages

💻 Quiz: DOM Manipulation


Project Challenge: Create a webpage with interactive buttons and text updates.

Chapter 7: Event Handling in JavaScript


7.1 Handling Events
• Adding and Removing Event Listeners
• Mouse and Keyboard Events (click, keydown, keyup)

7.2 Handling User Inputs

📌
• Form Validation and Error Handling

📝
💻
Case Study: Real-World JavaScript Form Validation
Quiz: Event Handling
Project Challenge: Build a login form with validation.

PART 6: HANDS-ON JAVASCRIPT


PROJECTS
Chapter 12: Beginner Projects
• Simple Calculator
• To-Do List App

Chapter 13: Intermediate Projects


• Weather App
• Digital Clock

Chapter 14: Advanced Projects


• E-commerce Shopping Cart

📌
• Real-Time Chat Application

📝
💻
Case Study: How JavaScript Powers Real-time Messaging Apps
Quiz: Scalable JavaScript Project Development
Final Project Challenge: Develop a task management app with authentication.
PART 7: MASTERING JAVASCRIPT FOR
CAREER GROWTH
Chapter 15: Writing Clean and Efficient Code
• Code Optimization Techniques

📌
• Best Practices for Readability

📝 Case Study: How Top Companies Maintain JavaScript Codebases


Quiz: JavaScript Performance Optimization

Chapter 1: Getting Started with JavaScript


JavaScript is one of the most powerful and versatile programming languages, playing a crucial role
in modern web development. Whether you're building interactive websites, backend applications, or
even mobile apps, JavaScript is an essential tool for every developer.
In this chapter, we'll explore what JavaScript is, why it matters, and how to set up your development
environment to start coding right away!

1.1 Introduction to JavaScript


What is JavaScript and Why is it Important?
JavaScript is a high-level, interpreted programming language designed to bring interactivity to
websites. Initially created for client-side scripting, it has now evolved into a full-fledged
programming language that runs on both the browser and the server.


Here’s why JavaScript is essential:



Universal: Runs in all modern web browsers without installation.
Versatile: Used for frontend, backend, game development, mobile apps, and even AI.

✅ Fast and Efficient: Executes directly in the browser without the need for compilation.
Massive Community: Extensive documentation, libraries, and frameworks to speed up
development.

How JavaScript Powers the Web


Think about websites like Facebook, Twitter, or YouTube. The animations, real-time notifications,
and interactive elements all rely on JavaScript. Behind the scenes, JavaScript interacts with HTML
and CSS to create dynamic experiences.
• HTML = The structure (skeleton) of a webpage
• CSS = The styling (colors, fonts, layout)
• JavaScript = The interactivity (animations, forms, dynamic content)
The Evolution of JavaScript
JavaScript has undergone significant updates over the years, improving its syntax, performance, and
usability.
• ES5 (2009): The first major standardized version of JavaScript.
• ES6 (2015): Introduced modern features like let, const, arrow functions, and template
literals.
• ESNext: Continuous updates bringing new features like async/await, optional chaining, and
improved object handling.

🟡 ☕ 🔵
JavaScript vs. Java vs. TypeScript
Feature JavaScript Java TypeScript
Type System Dynamic Static Static (Superset of JavaScript)
Execution Interpreted Compiled Compiled to JavaScript
Usage Web Development Enterprise Applications Scalable Web Apps

👉
Learning Curve Beginner-Friendly More Complex Requires JavaScript Knowledge
JavaScript is not Java! Despite the name similarity, they are completely different languages.

1.2 Setting Up Your Development Environment


Before we write our first JavaScript code, let’s set up the tools we need.

Choosing a Code Editor

🔹
To write JavaScript efficiently, you'll need a good code editor. Here are some popular options:

🔹
🔹
VS Code (Recommended) – Lightweight, fast, and packed with features.
Sublime Text – Simple and highly customizable.
Atom – Open-source and beginner-friendly.

Installing and Configuring VS Code


1️⃣Download VS Code from code.visualstudio.com.
2️⃣Install the following useful extensions:
• ESLint (for catching errors)
• Prettier (for code formatting)
• Live Server (to preview projects instantly)
3️⃣Open VS Code, create a new file, and save it as script.js.

Using Browser Developer Tools

🖥️
Browsers come with built-in tools to debug JavaScript. To open the developer console:

🍎
Windows/Linux: Press Ctrl + Shift + J in Chrome/Edge or F12.
Mac: Press Cmd + Option + J.

You'll see the JavaScript console, where you can run code, check errors, and debug programs.
1.3 Writing and Running JavaScript Code
Now, let’s write our first JavaScript program!

Hello, JavaScript!
Open VS Code, create a file called index.html, and add this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Test</title>
</head>
<body>
<h1>Welcome to JavaScript!</h1>
<script>
console.log("Hello, JavaScript!");
</script>
</body>
</html>

👉 Open this file in a browser and press F12 to open the console. You should see "Hello,
JavaScript!" printed.

Running JavaScript in the Console


Instead of using a file, you can write JavaScript directly in the browser console.
1️⃣Open the console (Ctrl + Shift + J or Cmd + Option + J).
2️⃣Type the following and press Enter:
console.log("Hello, JavaScript!");

You’ll see the output instantly! 🎉


Running JavaScript in Node.js
JavaScript isn’t limited to browsers. Node.js allows you to run JavaScript on servers.
1️⃣Download and install Node.js from nodejs.org.
2️⃣Open a terminal and type:
node -v

This will show the installed Node.js version. To test JavaScript in Node.js, create a new file
app.js and add:
console.log("Running JavaScript in Node.js!");

Run it in the terminal with:


node app.js
🔥 You just executed JavaScript outside the browser!

🎨 ⚙️
Frontend vs. Backend JavaScript
Feature Frontend JavaScript Backend JavaScript
Runs In Web Browser Server (Node.js)
UI Interactions, DOM Database Management, APIs,
Purpose
Manipulation Authentication
Common
React, Vue, Angular Express.js, Nest.js
Frameworks

Case Study: How Google Chrome’s V8 Engine Executes


JavaScript
The V8 Engine is Google Chrome's JavaScript engine, responsible for executing JavaScript code
efficiently. Here’s how it works:
1️⃣Parsing: Converts JavaScript code into a tree structure.
2️⃣Compilation: Uses Just-In-Time (JIT) compilation for fast execution.
3️⃣Execution: Runs optimized JavaScript in the browser.
Fun fact: Node.js is built on the V8 engine, which allows JavaScript to run on servers! 🚀
📌 Chapter Summary
JavaScript is a versatile language used for both frontend and backend development.
✔️Setting up a proper development environment is crucial for a smooth coding experience.
✔️JavaScript can be executed in the browser, in the console, or using Node.js.
✔️The V8 engine powers JavaScript execution in Chrome and Node.js.

📝 Quiz: JavaScript Basics


1️⃣What is JavaScript primarily used for?
a) Database management
b) Styling web pages
c) Making web pages interactive
d) Writing operating systems
2️⃣Which tool allows you to write JavaScript code in the browser?
a) Node.js
b) VS Code
c) Chrome DevTools
d) Sublime Text
3️⃣What is the difference between JavaScript and Java?
💻 Coding Challenge
✍️Task: Write a JavaScript program that asks for the user’s name and prints a custom greeting in
the console.
let name = prompt("What is your name?");
console.log(`Hello, ${name}! Welcome to JavaScript.`);

🚀 Run it in the console and see the magic!

Next Chapter: JavaScript Fundamentals


Now that you have a strong foundation, let’s dive deeper into JavaScript syntax, variables, and
operators!

Chapter 2: JavaScript Fundamentals


Now that we’ve set up our development environment and understand the basics of JavaScript, it’s


time to dive into the core building blocks of the language. This chapter will cover:



Variables and Data Types
Operators and Expressions
Comments and Code Readability

2.1 Variables in JavaScript


A variable is a container that stores data. In JavaScript, we use variables to hold information that
we can reference and manipulate later.

Declaring Variables
JavaScript provides three ways to declare variables:

✅ ✅
Keyword Scope Can be Reassigned? Hoisted?

✅ ❌
var Function Yes Yes (with undefined)

❌ ❌
let Block Yes No
const Block No No
Using var (Old Way - Not Recommended)
var name = "Alice";
console.log(name); // Alice

var name = "Bob"; // Re-declaring is allowed


console.log(name); // Bob
🔴 Problem: var allows re-declaration, which can lead to unexpected bugs.

Using let (Modern Way - Recommended)


let age = 25;
age = 30; // ✅
Allowed
console.log(age); // 30

✅ let is block-scoped and prevents accidental re-declaration.

Using const (For Constants - Recommended for Fixed Values)


const pi = 3.1416;
console.log(pi); // 3.1416

pi = 3.14; // ❌ Error: Assignment to constant variable

✅ const ensures the value cannot be changed after declaration.

2.2 Data Types in JavaScript


JavaScript has primitive and non-primitive data types.

Primitive Data Types (Stored by Value)


Data Type Example
String (Text) "Hello, world!"
Number (Integers & Floats) 42, 3.14
Boolean (True/False) true, false
Undefined (No Value) let x; (x is undefined)
Null (Empty Value) let y = null;
Symbol (Unique Identifier) Symbol("id")
BigInt (Large Numbers) BigInt(9999999999999)
Example Usage
let name = "Dhanian"; // String
let age = 27; // Number
let isDeveloper = true; // Boolean
let x; // Undefined
let y = null; // Null
let uniqueId = Symbol("id"); // Symbol
let bigNumber = 9007199254740991n; // BigInt

Non-Primitive (Reference) Data Types


These store references to memory locations.

Type Example
Object { name: "John", age: 30 }
Array [1, 2, 3, 4]
Function function greet() { console.log("Hello"); }
Example Usage
let person = { name: "Alice", age: 25 }; // Object
let numbers = [1, 2, 3, 4]; // Array
function sayHello() { console.log("Hello!"); } // Function

2.3 Operators in JavaScript


Operators perform operations on values and variables.

1. Arithmetic Operators
Operator Description Example
+ Addition 5 + 2 // 7
- Subtraction 5 - 2 // 3
* Multiplication 5 * 2 // 10
/ Division 5 / 2 // 2.5
% Modulus (Remainder) 5 % 2 // 1
** Exponentiation 2 ** 3 // 8
let a = 10;
let b = 3;
console.log(a % b); // 1
console.log(2 ** 4); // 16

2. Assignment Operators
Operator Description Example
= Assign x = 10
+= Add & Assign x += 5 // x = x + 5
-= Subtract & Assign x -= 5 // x = x - 5
*= Multiply & Assign x *= 2 // x = x * 2
let score = 50;
score += 10; // score = score + 10
console.log(score); // 60

3. Comparison Operators
Operator Description Example Result
== Equal To (Loose) 5 == "5" true
=== Equal & Type (Strict) 5 === "5" false
!= Not Equal To 5 != 3 true
!== Not Equal & Type 5 !== "5" true
> Greater Than 10 > 5 true
console.log(5 == "5"); // true (loose comparison)
console.log(5 === "5"); // false (strict comparison)
4. Logical Operators
Operator Description Example Result
&& AND true && false false
` ` OR
! NOT !true false
let isAdult = age >= 18 && age < 65; // True if age is between 18 and 64

2.4 Comments in JavaScript


Comments help explain code and improve readability.

Single-line Comment
// This is a single-line comment
console.log("Hello, JavaScript!");

Multi-line Comment
/*
This is a multi-line comment.
Useful for explaining complex logic.
*/
console.log("Hello again!");

📌 Chapter Summary
✔️JavaScript variables are declared using let, const, or var.
✔️Primitive data types include String, Number, Boolean, Undefined, Null, Symbol, and BigInt.
✔️Operators are used for arithmetic, assignment, comparison, and logic.
✔️Comments make code readable and maintainable.

📝 Quiz: JavaScript Fundamentals


1️⃣What is the difference between let and const?
2️⃣What will console.log(5 == "5") output?
3️⃣What is the output of console.log(3 + "3")?

💻 Coding Challenge
✍️Task: Create a program that asks for two numbers and prints their sum.
let num1 = prompt("Enter first number:");
let num2 = prompt("Enter second number:");
let sum = Number(num1) + Number(num2);
console.log(`The sum is: ${sum}`);

Next Chapter: JavaScript Control Flow (if, loops, switch)


Now that you understand JavaScript basics, let’s explore how to control the flow of execution!

Chapter 3: Control Flow and Loops


Now that we understand JavaScript fundamentals, let’s explore control flow—how JavaScript
makes decisions and repeats actions.


In this chapter, we’ll cover:


Conditional Statements (if, else, switch)


Loops (for, while, do...while)
Controlling Loop Execution (break, continue)

3.1 Conditional Statements


Conditional statements allow JavaScript to execute different code blocks based on conditions.

1. The if Statement
The if statement runs a block of code only if the condition is true.
let age = 18;

if (age >= 18) {


console.log("You are eligible to vote!");
}

2. The if...else Statement


If the condition is false, the else block runs.
let age = 16;

if (age >= 18) {


console.log("You can vote.");
} else {
console.log("Sorry, you must be 18 or older.");
}

3. The else if Statement


Use else if to check multiple conditions.
let score = 75;

if (score >= 90) {


console.log("Grade: A");
} else if (score >= 75) {
console.log("Grade: B");
} else {
console.log("Grade: C");
}

4. The Ternary Operator (condition ? value1 : value2)


A shorter way to write if...else statements.
let isMember = true;
let discount = isMember ? "10% off" : "No discount";
console.log(discount);

5. The switch Statement


The switch statement is used when comparing one value against multiple cases.
let day = "Monday";

switch (day) {
case "Monday":
console.log("Start of the week!");
break;
case "Friday":
console.log("Weekend is coming!");
break;
case "Sunday":
console.log("It's a rest day!");
break;
default:
console.log("Just another day.");
}

3.2 Loops in JavaScript


Loops allow JavaScript to repeat actions multiple times.

1. The for Loop


The for loop runs a block of code a specific number of times.
for (let i = 1; i <= 5; i++) {
console.log("Iteration:", i);
}

2. The while Loop


The while loop executes as long as the condition is true.
let count = 1;

while (count <= 5) {


console.log("Count:", count);
count++;
}

3. The do...while Loop


The do...while loop executes the block at least once before checking the condition.
let num = 1;

do {
console.log("Number:", num);
num++;
} while (num <= 5);

4. Loop Control Statements (break and continue)


Using break (Stop the Loop)
for (let i = 1; i <= 10; i++) {
if (i === 5) {
break; // Stops the loop when i equals 5
}
console.log(i);
}

Using continue (Skip an Iteration)


for (let i = 1; i <= 5; i++) {
if (i === 3) {
continue; // Skips 3
}
console.log(i);
}

📌 Case Study: How Social Media Feeds Use Loops


Social media platforms use loops to dynamically load more content as users scroll.
For example, Instagram or Twitter may use a loop like this:
let posts = ["Post 1", "Post 2", "Post 3"];

for (let post of posts) {


console.log("Loading:", post);
}

By continuously fetching new posts, platforms create an infinite scrolling experience.

📝 Quiz: Loops and Conditionals


1️⃣What is the difference between while and do...while loops?
2️⃣How can you stop a loop from executing?
3️⃣What does the following code output?
for (let i = 1; i <= 5; i++) {
if (i === 3) continue;
console.log(i);
}

💻 Project Challenge: Number-Guessing Game


✍️Task: Create a simple game where the user guesses a number between 1 and 10.
let secretNumber = Math.floor(Math.random() * 10) + 1;
let guess;

do {
guess = Number(prompt("Guess a number between 1 and 10:"));

if (guess > secretNumber) {


console.log("Too high! Try again.");
} else if (guess < secretNumber) {
console.log("Too low! Try again.");
} else {
console.log("Congratulations! You guessed it!");
}
} while (guess !== secretNumber);

Next Chapter: Functions and Execution Context


Now that we can control program flow, let’s explore functions—how to write reusable code and
understand execution context.

Chapter 4: Functions and Execution Context


Functions are the building blocks of JavaScript programs. They help us write reusable, modular,
and maintainable code.


In this chapter, we’ll cover:

✅ Function declarations and expressions


Arrow functions (=>)



Function parameters and return values
Function scope and closures
JavaScript execution context

4.1 Understanding Functions


A function is a block of reusable code that performs a specific task. Functions help keep your code
organized and DRY (Don’t Repeat Yourself).
1. Function Declaration
A function can be declared using the function keyword.
function greet() {
console.log("Hello, welcome to JavaScript!");
}

// Calling the function


greet();

2. Function Parameters and Arguments


Functions can take parameters (inputs) and return values.
function greetUser(name) {
console.log("Hello, " + name + "!");
}

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

3. The return Statement


A function can return a value using return.
function add(a, b) {
return a + b;
}

let sum = add(5, 3);


console.log(sum); // Output: 8

4.2 Function Expressions and Arrow Functions


1. Function Expressions
Functions can be stored in variables and passed around like values.
const multiply = function (a, b) {
return a * b;
};

console.log(multiply(4, 5)); // Output: 20

2. Arrow Functions (=>)


Arrow functions provide a shorter syntax.
const subtract = (a, b) => a - b;

console.log(subtract(10, 3)); // Output: 7

Arrow Function Variations


Single Parameter (No Parentheses Needed)
const square = x => x * x;
console.log(square(4)); // Output: 16

No Parameters (Use Empty Parentheses () )


const sayHello = () => console.log("Hello!");
sayHello();

4.3 Function Scope and Closures


1. Function Scope
Scope determines where variables can be accessed.

Local Scope (Inside a Function)


function example() {
let localVar = "I exist only inside this function";
console.log(localVar);
}

example();
// console.log(localVar); ❌ ERROR (localVar is not accessible outside)

Global Scope (Outside a Function)


let globalVar = "I exist everywhere!";

function showGlobal() {
console.log(globalVar);
}

showGlobal(); // Output: I exist everywhere!

2. Closures (Functions Inside Functions)


A closure is a function that remembers the variables from its parent scope, even after the parent
function has finished executing.
function outerFunction(outerValue) {
return function innerFunction(innerValue) {
return outerValue + innerValue;
};
}

const addFive = outerFunction(5); // `outerValue` is 5


console.log(addFive(10)); // Output: 15


Closures are powerful and used in real-world JavaScript for:



Data privacy
Event handlers
Functional programming
4.4 JavaScript Execution Context
1. How JavaScript Executes Code
JavaScript runs in two phases:
1️⃣Creation Phase:
• Memory is allocated for variables and functions.
• Functions are hoisted (moved to the top).
2️⃣Execution Phase:
• Code is executed line by line.
• Function calls create a new execution context in the Call Stack.

2. Understanding Hoisting
JavaScript hoists function declarations to the top before execution.
greet(); // ✅ Works because function declarations are hoisted

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

However, function expressions are NOT hoisted.


greet(); // ❌ ERROR: greet is not defined

const greet = function () {


console.log("Hello!");
};

📌 Case Study: How JavaScript Closures are Used in Event


Listeners
Closures are heavily used in event listeners to keep track of previous states.
For example, a counter button using closures:
function createCounter() {
let count = 0;

return function () {
count++;
console.log("Current Count:", count);
};
}

const counter = createCounter();

document.getElementById("countButton").addEventListener("click", counter);

Every time the button is clicked, the counter remembers the previous count due to closures. 🚀
📝 Quiz: Functions and Scope
1️⃣What is the difference between a function declaration and a function expression?
2️⃣What will this code output?
function outer() {
let x = 10;

function inner() {
console.log(x);
}

return inner;
}

const myFunction = outer();


myFunction();

3️⃣Why does this code throw an error?


console.log(double(4));

const double = function (num) {


return num * 2;
};

💻 Coding Challenge: Unique ID Generator


✍️Task: Create a function that generates unique IDs every time it's called.
function createIDGenerator() {
let id = 0;

return function () {
id++;
return `user_${id}`;
};
}

const generateID = createIDGenerator();

console.log(generateID()); // Output: user_1


console.log(generateID()); // Output: user_2
console.log(generateID()); // Output: user_3

Next Chapter: Working with Objects and Arrays


Now that we understand functions and scope, let’s dive into JavaScript objects and arrays,
which are key data structures for building real-world applications.
Chapter 5: Working with Objects and Arrays
Objects and arrays are fundamental data structures in JavaScript. They allow us to store,
organize, and manipulate data efficiently.


In this chapter, we’ll cover:



JavaScript objects and how to manipulate them
Working with arrays and common array methods



Object destructuring, spread, and rest operators
Iterating over objects and arrays
Sorting and searching data

5.1 JavaScript Objects


1. Introduction to Objects
An object is a collection of key-value pairs, where each key is a string (property name) and each
value can be any data type.
const user = {
name: "Alice",
age: 25,
isAdmin: true,
};

2. Accessing Object Properties


Dot Notation (.)
console.log(user.name); // Output: Alice

Bracket Notation ([])


Useful when accessing properties dynamically.
console.log(user["age"]); // Output: 25

3. Adding, Updating, and Deleting Properties


Add a new property:
user.email = "alice@example.com";

Update an existing property:


user.age = 26;

Delete a property:
delete user.isAdmin;
4. Object Methods (this Keyword)
An object method is a function stored as a property inside an object.
const user = {
name: "Alice",
greet: function () {
console.log(`Hello, my name is ${this.name}`);
},
};

user.greet(); // Output: Hello, my name is Alice

5.2 Object Destructuring, Spread, and Rest Operators


1. Object Destructuring
Extract values from an object into variables.
const user = { name: "Alice", age: 25 };

const { name, age } = user;


console.log(name); // Output: Alice
console.log(age); // Output: 25

2. Object Spread Operator ({...obj})


Create a copy of an object or merge multiple objects.
const user1 = { name: "Alice", age: 25 };
const user2 = { ...user1, email: "alice@example.com" };

console.log(user2);
// Output: { name: "Alice", age: 25, email: "alice@example.com" }

3. Object Rest Operator ({...rest})


Collect remaining properties.
const user = { name: "Alice", age: 25, email: "alice@example.com" };

const { name, ...rest } = user;


console.log(rest);
// Output: { age: 25, email: "alice@example.com" }

5.3 JavaScript Arrays


1. Creating and Accessing Arrays
An array is an ordered collection of values.
const colors = ["red", "green", "blue"];
console.log(colors[0]); // Output: red
2. Modifying Arrays
Add Elements:
colors.push("yellow"); // Adds to end
colors.unshift("pink"); // Adds to beginning

Remove Elements:
colors.pop(); // Removes last element
colors.shift(); // Removes first element

5.4 Iterating Over Arrays


1. forEach() - Loop Through an Array
colors.forEach((color) => {
console.log(color);
});

2. map() - Transform an Array


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

3. filter() - Get Specific Items


const ages = [12, 25, 17, 30];
const adults = ages.filter(age => age >= 18);
console.log(adults); // Output: [25, 30]

4. reduce() - Accumulate Values


const total = [10, 20, 30].reduce((sum, num) => sum + num, 0);
console.log(total); // Output: 60

5.5 Sorting and Searching in Arrays


1. Sorting Arrays (sort())
Sorting strings:
const names = ["Charlie", "Alice", "Bob"];
names.sort();
console.log(names); // Output: ["Alice", "Bob", "Charlie"]

Sorting numbers (ascending):


const numbers = [10, 5, 2, 8];
numbers.sort((a, b) => a - b);
console.log(numbers); // Output: [2, 5, 8, 10]
2. Searching in Arrays (find() and some())
Find an element:
const users = [{ name: "Alice" }, { name: "Bob" }];
const user = users.find(user => user.name === "Alice");
console.log(user); // Output: { name: "Alice" }

Check if an element exists:


const hasAdults = ages.some(age => age >= 18);
console.log(hasAdults); // Output: true

📌 Case Study: How E-commerce Sites Use Arrays for


Shopping Carts
Shopping carts in e-commerce websites use arrays to manage products.
const cart = [
{ product: "Laptop", price: 1000 },
{ product: "Mouse", price: 50 }
];

const totalPrice = cart.reduce((sum, item) => sum + item.price, 0);


console.log(totalPrice); // Output: 1050

📝 Quiz: Objects and Arrays


1️⃣How do you add a property to an object?
2️⃣What is the difference between map() and forEach()?
3️⃣How do you use the spread operator to copy an object?

💻 Project Challenge: Product Inventory System


✍️Task: Build a JavaScript-based product inventory system.
const inventory = [];

function addProduct(name, price) {


inventory.push({ name, price });
}

function listProducts() {
console.log("Inventory:", inventory);
}

addProduct("Phone", 800);
addProduct("Tablet", 500);
listProducts();

✅ Bonus: Add a function to find the most expensive product!


Next Chapter: Understanding the DOM
Now that we know how to work with objects and arrays, let’s explore DOM manipulation to
make interactive web pages.

Chapter 6: Understanding the DOM


The Document Object Model (DOM) is a programming interface for web pages. It represents
the structure of an HTML document as a tree of objects, allowing JavaScript to dynamically
manipulate content, styles, and behavior.


In this chapter, we’ll cover:



How the DOM represents an HTML document
Selecting and manipulating elements

✅ Changing content, attributes, and styles with JavaScript


The difference between innerHTML, textContent, and innerText
Creating, updating, and removing elements dynamically

6.1 Introduction to the DOM


1. What is the DOM?
The DOM allows JavaScript to interact with HTML elements. Every element in a web page is
represented as a node in a hierarchical tree.
Consider this simple HTML structure:
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1 id="title">Hello, JavaScript!</h1>
<p class="description">This is a simple paragraph.</p>
</body>
</html>

This structure is represented in the DOM as:


Document
├── <html>
│ ├── <head>
│ │ └── <title>My Page</title>
│ ├── <body>
│ │ ├── <h1 id="title">Hello, JavaScript!</h1>
│ │ ├── <p class="description">This is a simple paragraph.</p>
2. How Browsers Interpret the DOM
When a web page loads, the browser:
1️⃣Parses the HTML and constructs the DOM tree
2️⃣Loads CSS and applies styles
3️⃣Executes JavaScript to manipulate the DOM
4️⃣Renders the final page
Using JavaScript, we can:
✔ Add, remove, or modify elements dynamically
✔ Change text and attributes
✔ Handle user events (e.g., button clicks)

6.2 Selecting and Manipulating Elements


To manipulate the DOM, we first need to select elements.

1. Selecting Elements
Using getElementById() (Selects a single element by ID)
const title = document.getElementById("title");
console.log(title.innerText); // Output: Hello, JavaScript!

Using getElementsByClassName() (Selects multiple elements with the same class)


const paragraphs = document.getElementsByClassName("description");
console.log(paragraphs[0].innerText);

Using getElementsByTagName() (Selects multiple elements by tag name)


const allParagraphs = document.getElementsByTagName("p");
console.log(allParagraphs.length); // Number of <p> elements

Using querySelector() (Selects the first matching element)


const firstParagraph = document.querySelector(".description");
console.log(firstParagraph.innerText);

Using querySelectorAll() (Selects all matching elements)


const allParagraphs = document.querySelectorAll("p");
allParagraphs.forEach(p => console.log(p.innerText));
2. Changing Content and Attributes
Modifying Text Content
✔ Using innerText (Shows visible text)
✔ Using textContent (Includes hidden text)
✔ Using innerHTML (Allows inserting HTML)
title.innerText = "Welcome to JavaScript!";
title.textContent = "This is a new heading.";
title.innerHTML = "<span style='color:blue'>Styled Text</span>";

Changing Attributes
const link = document.querySelector("a");
link.setAttribute("href", "https://example.com");
link.setAttribute("target", "_blank"); // Open in new tab
console.log(link.getAttribute("href")); // Output: https://example.com

Modifying Styles with JavaScript


title.style.color = "red";
title.style.fontSize = "24px";

✔ Adding a CSS class


title.classList.add("highlight");

✔ Removing a CSS class


title.classList.remove("highlight");

✔ Toggling a CSS class


title.classList.toggle("dark-mode");

6.3 Creating, Updating, and Removing Elements


1. Creating New Elements
const newParagraph = document.createElement("p");
newParagraph.innerText = "This is a dynamically created paragraph.";
document.body.appendChild(newParagraph);

✔ Appending an element inside another element


document.getElementById("title").appendChild(newParagraph);

2. Removing Elements
const paragraph = document.querySelector(".description");
paragraph.remove();

✔ Removing an element using removeChild()


document.body.removeChild(paragraph);

📌 Case Study: How JavaScript is Used in Interactive Web


Pages
Modern web applications use JavaScript and the DOM to create dynamic user experiences.
For example, social media websites use JavaScript to:
✔ Load new content dynamically (infinite scrolling)
✔ Show or hide elements (modals, dropdowns)
✔ Validate forms before submission
Let’s see an example of a live text update when a button is clicked:
<h1 id="greeting">Hello, Guest!</h1>
<button id="changeText">Click Me</button>

<script>
document.getElementById("changeText").addEventListener("click", function () {
document.getElementById("greeting").innerText = "Welcome to JavaScript!";
});
</script>

✔ When the button is clicked, the heading text changes dynamically! 🎉


📝 Quiz: DOM Manipulation
1️⃣What is the difference between innerText, textContent, and innerHTML?
2️⃣How do you select all <p> elements using JavaScript?
3️⃣What method allows you to add a new element to the DOM?

💻 Project Challenge: Interactive Web Page


✍️Task: Build a simple webpage with interactive elements.
<h1 id="title">Click the button to change color</h1>
<button id="changeColor">Change Color</button>

<script>
document.getElementById("changeColor").addEventListener("click", function () {
document.getElementById("title").style.color = "blue";
});
</script>

✅ Bonus Challenge: Create a light mode/dark mode toggle!


Next Chapter: Event Handling in JavaScript
Now that we can manipulate the DOM, let's dive into event handling and user interactions.

Chapter 7: Event Handling in JavaScript


Interactivity is a crucial part of modern web applications. JavaScript provides event handling
mechanisms that allow us to respond to user actions like clicks, key presses, form submissions,
and more.


In this chapter, we’ll cover:



What events are and how they work
How to add event listeners to elements



Mouse, keyboard, and form events
Event propagation (bubbling & capturing)
Preventing default behavior and event delegation

7.1 Handling Events


1. What Are Events?
Events are actions that happen in the browser, such as:
✔ A button click
✔ A mouse movement
✔ A form submission
✔ A key press
JavaScript allows us to listen for these events and execute code in response.

2. Adding Event Listeners


We can use addEventListener() to attach an event to an element.

Example: Click Event


<button id="myButton">Click Me</button>

<script>
document.getElementById("myButton").addEventListener("click", function () {
alert("Button Clicked!");
});
</script>

📌 Explanation:
✔ The event listener waits for a click event.
✔ When the button is clicked, an alert box appears.
3. Different Ways to Handle Events
1️⃣ Using Inline Event Handlers (Not Recommended)
<button onclick="alert('Button Clicked!')">Click Me</button>

📌 Downside: Makes HTML messy and hard to maintain.

2️⃣ Using the onclick Property


<button id="btn">Click Me</button>

<script>
const button = document.getElementById("btn");
button.onclick = function () {
alert("Button Clicked!");
};
</script>

📌 Downside: Overwrites previous event handlers.

3️⃣ Using addEventListener() (Best Practice)


<button id="btn">Click Me</button>

<script>
const button = document.getElementById("btn");
button.addEventListener("click", function () {
alert("Button Clicked!");
});
</script>

📌✅ Recommended: Allows multiple event handlers on the same element.

7.2 Mouse and Keyboard Events


1. Mouse Events
Event Name Description
click Fires when an element is clicked
dblclick Fires on double-click
mousedown Fires when mouse button is pressed
mouseup Fires when mouse button is released
mousemove Fires when mouse moves over an element
mouseenter Fires when mouse enters an element
Example: Mouse Hover Event
<div id="box" style="width:100px; height:100px; background:red;"></div>

<script>
const box = document.getElementById("box");
box.addEventListener("mouseenter", function () {
box.style.backgroundColor = "blue";
});

box.addEventListener("mouseleave", function () {
box.style.backgroundColor = "red";
});
</script>

📌 Result: The box changes color when hovered over.

2. Keyboard Events
Event Name Description
keydown Fires when a key is pressed
keyup Fires when a key is released
keypress Fires when a key is pressed and released (deprecated)
Example: Detecting Key Press
<input type="text" id="inputBox" placeholder="Type something...">

<script>
document.getElementById("inputBox").addEventListener("keydown", function
(event) {
console.log("Key pressed:", event.key);
});
</script>

📌 Result: Logs the pressed key in the console.

7.3 Event Propagation (Bubbling & Capturing)


Events in JavaScript follow a propagation model, meaning they move through the DOM
hierarchy.

1. Bubbling (Default Behavior)


Events travel from the target element up to the root.
<div id="parent">
<button id="child">Click Me</button>
</div>

<script>
document.getElementById("parent").addEventListener("click", function () {
alert("Parent Clicked!");
});

document.getElementById("child").addEventListener("click", function () {
alert("Child Clicked!");
});
</script>
📌 Result: Clicking the button first triggers "Child Clicked!", then "Parent
Clicked!".

2. Capturing (Reverse of Bubbling)


Events travel from the root element down to the target.
document.getElementById("parent").addEventListener(
"click",
function () {
alert("Parent Clicked!");
},
true
);

📌 Notice: Adding true enables capturing phase.

7.4 Preventing Default Behavior


Some elements (like forms and links) have default behaviors. We can prevent them using
event.preventDefault().

Example: Preventing Form Submission


<form id="myForm">
<input type="text" placeholder="Enter name">
<button type="submit">Submit</button>
</form>

<script>
document.getElementById("myForm").addEventListener("submit", function (event)
{
event.preventDefault();
alert("Form submission prevented!");
});
</script>

📌 Result: The form will not submit.

7.5 Event Delegation


Instead of adding many event listeners, we can use event delegation to listen on a parent
element.

Example: Using Event Delegation for Multiple Buttons


<div id="buttonContainer">
<button class="btn">Button 1</button>
<button class="btn">Button 2</button>
<button class="btn">Button 3</button>
</div>
<script>
document.getElementById("buttonContainer").addEventListener("click", function
(event) {
if (event.target.classList.contains("btn")) {
alert(event.target.innerText + " Clicked!");
}
});
</script>

📌
✅ Why?

✅ Efficient when handling many dynamic elements


Avoids adding multiple event listeners

📌 Case Study: How JavaScript Form Validation Works in


Real-World Applications
JavaScript event handling is crucial for form validation. Consider a real-world login form:
<form id="loginForm">
<input type="text" id="username" placeholder="Username">
<input type="password" id="password" placeholder="Password">
<button type="submit">Login</button>
</form>

<script>
document.getElementById("loginForm").addEventListener("submit", function
(event) {
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;

if (username === "" || password === "") {


event.preventDefault();
alert("Username and password cannot be empty!");
}
});
</script>

✔ Ensures required fields are filled before submission

📝 Quiz: Event Handling


1️⃣What’s the difference between click and dblclick events?
2️⃣How do you prevent a form from submitting?
3️⃣What is event delegation, and why is it useful?

💻
🔹
Project Challenge: Create a Login Form with Validation

🔹
🔹
Validate that the username is at least 3 characters long
Password must be at least 6 characters
Display error messages dynamically
Chapter 8: Working with JavaScript Objects
and Arrays
Objects and arrays are fundamental data structures in JavaScript. They help us store, organize, and
manipulate data efficiently.


In this chapter, you’ll learn:



How to create and manipulate objects
How to work with arrays and array methods



The difference between mutable and immutable operations
Object destructuring and spread/rest operators
Using objects and arrays in real-world applications

8.1 Understanding JavaScript Objects


1. What Are Objects?
An object in JavaScript is a collection of key-value pairs.
✔ Keys (or properties) are strings.
✔ Values can be any data type (strings, numbers, arrays, functions, etc.).

Example: Creating an Object


const person = {
name: "Alice",
age: 25,
isStudent: false,
greet: function () {
console.log("Hello, my name is " + this.name);
},
};

console.log(person.name); // Alice
person.greet(); // Hello, my name is Alice

📌 Key Takeaways:
✔ Objects group related data and behavior together.
✔ this refers to the current object when inside a method.

2. Adding, Updating, and Deleting Properties


Adding a New Property
person.city = "New York";
console.log(person.city); // New York
Updating an Existing Property
person.age = 30;
console.log(person.age); // 30

Deleting a Property
delete person.isStudent;
console.log(person); // isStudent is removed

3. Nested Objects
Objects can contain other objects inside them.
const student = {
name: "John",
subjects: {
math: "A",
science: "B+",
},
};

console.log(student.subjects.math); // A

✔ Use dot notation (.) or bracket notation ([]) to access nested properties.

8.2 JavaScript Arrays


1. What Are Arrays?
An array is a special type of object that holds multiple values in order.
const colors = ["red", "blue", "green"];
console.log(colors[0]); // red

✔ Arrays are zero-based, meaning the first item is at index 0.

2. Adding and Removing Elements


1️⃣ Adding Elements
colors.push("yellow"); // Adds to the end
colors.unshift("black"); // Adds to the beginning
console.log(colors); // ["black", "red", "blue", "green", "yellow"]

2️⃣ Removing Elements


colors.pop(); // Removes last element
colors.shift(); // Removes first element
console.log(colors); // ["red", "blue", "green"]
3. Looping Through Arrays
Using forEach()
colors.forEach((color) => console.log(color));

Using for...of
for (let color of colors) {
console.log(color);
}

✔ Best Practice: Use forEach() for simple iteration.

8.3 Useful Array Methods


Method Description
push() Adds an item to the end
pop() Removes last item
shift() Removes first item
unshift() Adds an item to the beginning
slice() Extracts a portion of an array
splice() Adds/removes items at specific indexes
map() Transforms an array
filter() Filters an array based on a condition
reduce() Reduces an array to a single value
find() Returns the first item that matches a condition

1. map(): Transforming Arrays


✔ Creates a new array by applying a function to each element.
const numbers = [1, 2, 3, 4];
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8]

2. filter(): Selecting Specific Items


✔ Returns a new array with elements that satisfy a condition.
const scores = [80, 45, 90, 60, 30];
const passing = scores.filter((score) => score >= 50);
console.log(passing); // [80, 90, 60]

3. reduce(): Combining Array Elements


✔ Reduces an array to a single value (sum, product, etc.).
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 10

8.4 Destructuring and Spread Operator


1. Object Destructuring
✔ Extract properties from objects easily.
const user = { name: "Jane", age: 28 };
const { name, age } = user;
console.log(name); // Jane
console.log(age); // 28

2. Array Destructuring
✔ Extract values from arrays.
const fruits = ["apple", "banana", "cherry"];
const [first, second] = fruits;
console.log(first); // apple
console.log(second); // banana

3. Spread Operator (...)


✔ Copies all elements from one array or object.
const newColors = [...colors, "purple"];
console.log(newColors);

✔ Combines multiple arrays


const combined = [...colors, ...fruits];
console.log(combined);

8.5 Real-World Example: To-Do List App


Let’s build a simple to-do list using an array of objects.
let tasks = [
{ id: 1, title: "Buy groceries", completed: false },
{ id: 2, title: "Read JavaScript book", completed: true },
];

// Add a task
tasks.push({ id: 3, title: "Workout", completed: false });

// Mark a task as completed


tasks = tasks.map((task) =>
task.id === 1 ? { ...task, completed: true } : task
);
// Remove completed tasks
tasks = tasks.filter((task) => !task.completed);

console.log(tasks);

✔ Uses map() to update an item


✔ Uses filter() to remove completed tasks

📌 Quiz: Objects and Arrays


1️⃣How do you add a new property to an object?
2️⃣What is the difference between push() and unshift()?
3️⃣What does map() return?

💻
🔹
Project Challenge: Build a Movie List

🔹
🔹
Store movies in an array of objects
Allow users to add, remove, and filter movies
Use map(), filter(), and reduce()

Chapter 9: Functions and Execution Context


Functions are the backbone of JavaScript programming. They allow developers to write
efficient, reusable, and modular code. Mastering functions is essential for writing scalable
applications and working with JavaScript frameworks like React, Vue, or Node.js.


In this chapter, you’ll learn:



The different ways to declare functions in JavaScript
How to use parameters, arguments, and the return statement



The difference between function declarations, expressions, and arrow functions
The concept of function scope and closures
How execution context and the call stack work in JavaScript
By the end of this chapter, you will be able to write advanced functions and understand how
JavaScript executes function calls under the hood.

9.1 What is a Function?


A function is a self-contained block of code that performs a specific task when called.
1. Why Do We Need Functions?
Imagine Dhanian is developing a calculator app. Instead of writing repetitive code for each
calculation, he creates functions that handle different operations.

Without Functions (Repetitive Code)


let num1 = 10;
let num2 = 5;

console.log(num1 + num2); // 15
console.log(num1 - num2); // 5
console.log(num1 * num2); // 50

🔴 Problem: If Dhanian wants to perform more calculations, he will need to repeat the code
multiple times, making the program inefficient.

With Functions (Reusable Code)


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

function subtract(a, b) {
return a - b;
}

console.log(add(10, 5)); // 15
console.log(subtract(10, 5)); // 5

✔ Functions allow us to write once and reuse multiple times.

9.2 Declaring and Calling Functions


There are three ways to declare functions in JavaScript:
1️⃣Function Declaration
2️⃣Function Expression
3️⃣Arrow Function
Let’s explore each in detail.

1. Function Declaration
A function declaration is created using the function keyword.

Example: Function Declaration


function greet() {
console.log("Hello, welcome to JavaScript!");
}

To execute the function, call it using its name followed by parentheses:


greet(); // Output: Hello, welcome to JavaScript!
✔ Function declarations are hoisted (can be called before they are defined).

2. Function Expression
A function expression assigns a function to a variable.

Example: Function Expression


const greetUser = function() {
console.log("Hello, this is a function expression.");
};

greetUser(); // Output: Hello, this is a function expression.

🔴 Difference from function declarations: Function expressions are not hoisted (they must be
defined before calling).

3. Arrow Functions (=>)


Arrow functions are a modern ES6 feature that provides a shorter syntax for writing functions.

Example: Arrow Function


const multiply = (a, b) => a * b;

console.log(multiply(4, 5)); // Output: 20

✔ If there’s only one parameter, you can remove the parentheses:


const square = num => num * num;

console.log(square(6)); // Output: 36

✔ If there’s only one line of code, {} and return can be omitted.

9.3 Function Parameters and Return Values


1. Parameters vs. Arguments
✔ Parameters are placeholders inside function definitions.
✔ Arguments are actual values passed when calling a function.

Example: Function with Parameters


function introduce(name, age) {
console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}

introduce("Dhanian", 25);
// Output: Hello, my name is Dhanian and I am 25 years old.
2. Returning Values from Functions
Functions can return values using the return statement.

Example: Function with Return


function addNumbers(a, b) {
return a + b;
}

let result = addNumbers(7, 3);


console.log(result); // Output: 10

🔹 A function stops execution once it reaches return.

9.4 Function Scope


Scope determines where variables can be accessed.

1. Global Scope
A variable declared outside a function is global and can be accessed anywhere.
let globalVar = "I am global";

function checkScope() {
console.log(globalVar); // Accessible
}

checkScope();
console.log(globalVar); // Accessible

2. Local (Function) Scope


A variable declared inside a function is only accessible within that function.
function testScope() {
let localVar = "I am local";
console.log(localVar); // Accessible here
}

testScope();
console.log(localVar); // ❌ Error: localVar is not defined

✔ Best Practice: Minimize global variables to avoid conflicts.

9.5 Closures: Functions That Remember


A closure is a function that remembers variables from its outer scope even after the function has
finished executing.
Example: Closure in Action
function counter() {
let count = 0;

return function() {
count++;
console.log(count);
};
}

const increment = counter();

increment(); // Output: 1
increment(); // Output: 2
increment(); // Output: 3

✔ Closures help maintain state in JavaScript applications.

9.6 Execution Context and Call Stack


JavaScript executes functions using a call stack.

Example: Call Stack in Action


function first() {
console.log("First function");
second();
}

function second() {
console.log("Second function");
third();
}

function third() {
console.log("Third function");
}

first();

📌 Call Stack Execution:


1️⃣first() is called → added to the stack.
2️⃣second() is called → added to the stack.
3️⃣third() is called → added to the stack.
4️⃣third() finishes → removed from the stack.
5️⃣second() finishes → removed from the stack.
6️⃣first() finishes → removed from the stack.

📌 Case Study: How Closures Help in Web Development


Closures are widely used in JavaScript event listeners and data privacy.
Example: Private Counter Using Closures
function createCounter() {
let count = 0;

return {
increment: function() {
count++;
console.log(`Count: ${count}`);
}
};
}

const myCounter = createCounter();


myCounter.increment(); // Count: 1
myCounter.increment(); // Count: 2

✔ Count remains private and cannot be accessed directly.

Chapter 10: Asynchronous JavaScript &


Callbacks
JavaScript is single-threaded, meaning it executes one task at a time. But in modern applications,

✅ 📡🎭
we often need to:



Fetch data from APIs


Handle user interactions

✅ Delay execution
Process large amounts of data without freezing the UI


To achieve this, JavaScript uses asynchronous programming. In this chapter, we’ll explore:



What asynchronous JavaScript is and why it’s important
The event loop and how JavaScript handles async tasks

✅ How to use callbacks, promises, and async/await


Common real-world use cases for async programming

10.1 What is Asynchronous JavaScript?


Asynchronous JavaScript allows code to execute tasks in the background while the rest of the

🔹
program continues running.
Imagine Dhanian is building a weather app. The app must:
1️⃣Request weather data from an API
2️⃣Wait for the API to respond 📡
3️⃣Display the weather information to the user 📋
If JavaScript were synchronous, the entire app would freeze while waiting for the API!
10.2 Synchronous vs. Asynchronous Execution
1. Synchronous Execution (Blocking Code)
In synchronous execution, each line waits for the previous one to finish.

🍳🥕
Example: Synchronous Code
console.log("Step 1: Start cooking ");
console.log("Step 2: Chop vegetables
console.log("Step 3: Serve the meal
");
"); 🍽️
✅ Output (Runs in Order)
Step 1: Start cooking 🍳🥕
Step 2: Chop vegetables
Step 3: Serve the meal 🍽️
🔴 Problem: If a task takes too long, everything stalls!

2. Asynchronous Execution (Non-Blocking Code)


Using async techniques, JavaScript can handle multiple tasks at once.

🍳
Example: Asynchronous Code (setTimeout)
console.log("Step 1: Start cooking ");

setTimeout(() => {
console.log("Step 2: Chop vegetables
}, 2000); // Delay for 2 seconds
🥕 ");

console.log("Step 3: Serve the meal 🍽️ ");

✅ Output (Non-Blocking)
Step 1: Start cooking 🍳🍽️
Step 3: Serve the meal
Step 2: Chop vegetables 🥕 (After 2 seconds)

✔ The program doesn’t freeze while waiting!

10.3 Understanding the Event Loop


The event loop allows JavaScript to handle multiple tasks at once by:
1️⃣Running synchronous code first (main thread)
2️⃣Moving async tasks (like API calls, timers) to a separate queue
3️⃣Returning the results once the main thread is free
🔹 Example of Event Loop in Action
1️⃣
console.log(" Synchronous Task");

setTimeout(() => {
2️⃣
console.log(" Asynchronous Task (Delayed)");
}, 2000);

3️⃣
console.log(" Another Synchronous Task");

✅ Execution Order:
1️⃣ Synchronous Task
3️⃣ Another Synchronous Task
(After 2s) 2️⃣ Asynchronous Task (Delayed)

✔ JavaScript doesn’t wait for setTimeout before continuing execution.

10.4 Callbacks: The First Approach to Asynchronous


JavaScript
A callback is a function passed as an argument to another function. It runs after an
asynchronous task completes.

1. Callback Example: Simulating an API Request


function fetchData(callback) {
console.log("Fetching data... 📡 ");

setTimeout(() => {
let data = { user: "Dhanian", age: 25 };
callback(data);
}, 2000);
}

function displayData(data) {
console.log(`User: ${data.user}, Age: ${data.age}`);
}

fetchData(displayData);

✅ Output
Fetching data... 📡
(After 2 seconds) User: Dhanian, Age: 25

✔ The callback executes only when data is ready.

2. Callback Hell: The Downside of Callbacks

🔴
Callbacks can lead to nested functions, making code hard to read and debug.
Example: Callback Hell (Nested Callbacks)
function getUserData(callback) {
setTimeout(() => {
console.log("User data fetched
callback();
📡 ");
}, 1000);
}

function getPosts(callback) {
setTimeout(() => {
console.log("User posts fetched
callback();
📝 ");

}, 1000);
}

function getComments() {
setTimeout(() => {
console.log("User comments fetched
}, 1000);
💬 ");

getUserData(() => {
getPosts(() => {
getComments();
});
});

✅ Output (Nested Execution)


User data fetched 📡📝
User posts fetched
User comments fetched 💬
🔴 Problem: Too many nested callbacks make it difficult to manage.

10.5 Promises: A Better Way


A promise is an object that represents the future result of an async task. It can have three states:


1️⃣Pending (Waiting for the task to complete)


2️⃣Resolved (Success )
3️⃣Rejected (Failure )

1. Creating a Promise
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
let success = true;
if (success) {
resolve("Data fetched successfully! "); 📡
} else {

}
reject("Failed to fetch data "); ❌
}, 2000);
});

fetchData
.then(result => console.log(result)) // Runs on success
.catch(error => console.log(error)); // Runs on failure

✅ Output (If successful)


Data fetched successfully! 📡
✅ Output (If failed)
Failed to fetch data ❌
✔ Promises remove callback hell and improve readability.

10.6 Async/Await: The Modern Approach


🔹 async/await makes working with promises easier and looks like synchronous code.

1. Example: Fetching User Data Using Async/Await


function getUser() {
return new Promise(resolve => {
setTimeout(() => {
resolve({ name: "Dhanian", age: 25 });
}, 2000);
});
}

async function displayUser() {


console.log("Fetching user data...
let user = await getUser();
"); 📡
console.log(`User: ${user.name}, Age: ${user.age}`);
}

displayUser();

✅ Output
Fetching user data... 📡
(After 2 seconds) User: Dhanian, Age: 25

✔ Easier to read than .then() and avoids nesting.

📌 Case Study: How JavaScript Handles API Requests

🔹
Web apps often fetch data from external APIs.
Example: Fetching Data from a REST API
async function fetchData() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/users/1");
let data = await response.json();
console.log(data);
} catch (error) {

}
console.log("Error fetching data ❌
", error);

fetchData();
✅ Real-world scenario: Fetching user data in an app

Chapter 11: Working with JavaScript Objects


& Arrays
✅ 📋🗂️
In JavaScript, objects and arrays are fundamental data structures.

✅ Objects store data in key-value pairs


Arrays store data in an ordered list


In this chapter, we’ll explore:



Objects: How to create, modify, and access properties
Arrays: How to store and manipulate lists of data

✅ Common methods and operations for objects and arrays


Best practices and real-world use cases

11.1 JavaScript Objects: Key-Value Pairs


An object is a collection of properties, where each property has a key (name) and a value.

1. Creating an Object
Let's create an object representing Dhanian’s profile:
const user = {
name: "Dhanian",
age: 25,
country: "Kenya",
skills: ["JavaScript", "React", "Node.js"],
isDeveloper: true
};

✅ Key-value pairs:
• "name" → "Dhanian" (String)
• "age" → 25 (Number)
• "country" → "Kenya" (String)
• "skills" → ["JavaScript", "React", "Node.js"] (Array)
• "isDeveloper" → true (Boolean)

🔹
2. Accessing Object Properties
Using dot notation:
console.log(user.name); // "Dhanian"
console.log(user.age); // 25

🔹 Using bracket notation:


console.log(user["country"]); // "Kenya"

✔ Use dot notation unless the property name has spaces or special characters.

3. Modifying Object Properties


You can update properties like this:
user.age = 26;
console.log(user.age); // 26

Or add a new property:


user.email = "dhanian@example.com";
console.log(user.email); // "dhanian@example.com"

To delete a property:
delete user.isDeveloper;
console.log(user.isDeveloper); // undefined

4. Object Methods (Functions Inside Objects)


An object can contain functions:
const user = {
name: "Dhanian",
greet: function() {
return `Hello, my name is ${this.name}`;
}
};

console.log(user.greet()); // "Hello, my name is Dhanian"

✔ this refers to the current object.

11.2 JavaScript Arrays: Ordered Lists


An array is a special variable that can hold multiple values in an ordered manner.

1. Creating an Array
Let’s create an array of programming languages:
const languages = ["JavaScript", "Python", "Java", "C++"];
2. Accessing Array Elements
Arrays use zero-based indexing (first item = index 0).
console.log(languages[0]); // "JavaScript"
console.log(languages[2]); // "Java"

3. Modifying an Array
Updating a value:
languages[1] = "TypeScript";
console.log(languages); // ["JavaScript", "TypeScript", "Java", "C++"]

Adding a new value:


languages.push("Ruby");
console.log(languages); // ["JavaScript", "TypeScript", "Java", "C++", "Ruby"]

Removing the last item:


languages.pop();
console.log(languages); // ["JavaScript", "TypeScript", "Java", "C++"]

11.3 Common Array Methods

🔹
1. Adding & Removing Elements

🔹
push() → Adds to the end

🔹
pop() → Removes the last item

🔹
unshift() → Adds to the beginning
shift() → Removes the first item

Example:
let colors = ["Red", "Blue", "Green"];

colors.push("Yellow"); // ["Red", "Blue", "Green", "Yellow"]


colors.pop(); // ["Red", "Blue", "Green"]
colors.unshift("Purple"); // ["Purple", "Red", "Blue", "Green"]
colors.shift(); // ["Red", "Blue", "Green"]

🔹
2. Finding & Filtering Elements

🔹
indexOf() → Finds an element’s position

🔹
includes() → Checks if an element exists
filter() → Returns elements that match a condition

Example:
let numbers = [10, 20, 30, 40, 50];
console.log(numbers.indexOf(30)); // 2
console.log(numbers.includes(25)); // false

let greaterThan25 = numbers.filter(num => num > 25);


console.log(greaterThan25); // [30, 40, 50]

🔹
3. Looping Through Arrays
Using forEach()
let fruits = ["Apple", "Banana", "Cherry"];

fruits.forEach(fruit => console.log(fruit));

✅ Output:
Apple
Banana
Cherry

🔹 Using map() (Returns a new array)


let doubled = numbers.map(num => num * 2);
console.log(doubled); // [20, 40, 60, 80, 100]

📌 Case Study: Storing and Managing Users


Imagine we need to store multiple users in an app. We can use arrays of objects.
const users = [
{ id: 1, name: "Dhanian", role: "Developer" },
{ id: 2, name: "Alice", role: "Designer" },
{ id: 3, name: "Bob", role: "Manager" }
];

// Finding a user by ID
let user = users.find(user => user.id === 2);
console.log(user); // { id: 2, name: "Alice", role: "Designer" }

11.4 Best Practices for Using Objects & Arrays


✔ Use meaningful keys (user.name is better than user.n)
✔ Avoid modifying objects directly (use Object.assign() or spread syntax)
✔ Use array methods (map, filter, find) for cleaner code
Chapter 12: JavaScript ES6+ Features
(Modern JavaScript)
Why ES6 Matters?
JavaScript ES6 (ECMAScript 2015) and beyond introduced powerful features that improve code


readability, efficiency, and maintainability.



Let & Const: Better variable declarations
Arrow Functions: Shorter & cleaner functions



Template Literals: Easier string manipulation
Destructuring & Spread Operator: Faster object & array handling
Promises & Async/Await: Modern approach to asynchronous programming
Let’s explore these features in detail with examples, use cases, and best practices!

12.1 Let & Const: Block-Scoped Variables

🔹
Before ES6, JavaScript had only var, which had issues with function scope and hoisting.

let and const are block-scoped (curly braces {} define their scope).

1. Using let (for variables that can change)


let name = "Dhanian";
name = "John"; // ✅
Allowed
console.log(name); // "John"

✔ let allows reassignment, but is limited to its block scope.

2. Using const (for constants that don’t change)


const age = 25;
age = 30; // ❌
ERROR: Cannot reassign a `const` variable

✔ const prevents accidental reassignment.

✔ Best practice: Use const by default, and only use let if you need to reassign the value.

12.2 Arrow Functions: Shorter & Cleaner Functions


Arrow functions (=>) make functions shorter, cleaner, and more readable.

1. Traditional Function
function greet(name) {
return `Hello, ${name}`;
}
console.log(greet("Dhanian")); // "Hello, Dhanian"

2. Arrow Function Equivalent


const greet = (name) => `Hello, ${name}`;
console.log(greet("Dhanian")); // "Hello, Dhanian"

✔ No need for {} or return if it’s a one-liner!

✔ this behaves differently in arrow functions (explained below).

3. Arrow Functions with Multiple Parameters


const add = (a, b) => a + b;
console.log(add(5, 3)); // 8

✔ Cleaner syntax for callbacks and array methods:


const numbers = [1, 2, 3, 4, 5];
const squares = numbers.map(num => num * num);
console.log(squares); // [1, 4, 9, 16, 25]

12.3 Template Literals: Better String Handling


Template literals (` `) allow embedded expressions using ${}.

1. Traditional String Concatenation


const name = "Dhanian";
console.log("Hello, " + name + "!"); // "Hello, Dhanian!"

2. Using Template Literals


const name = "Dhanian";
console.log(`Hello, ${name}!`); // "Hello, Dhanian!"

✔ Multiline Strings Without \n


const bio = `Hi, I'm Dhanian.
I am a JavaScript Developer.
I love coding.`;

console.log(bio);

12.4 Destructuring Objects & Arrays


Destructuring allows extracting values from objects or arrays into variables easily.
1. Destructuring Objects
const user = { name: "Dhanian", age: 25, country: "Kenya" };

const { name, age } = user;


console.log(name, age); // "Dhanian" 25

✔ No need to write user.name or user.age manually!

✔ Renaming while destructuring


const { name: fullName } = user;
console.log(fullName); // "Dhanian"

2. Destructuring Arrays
const colors = ["Red", "Blue", "Green"];

const [first, second] = colors;


console.log(first, second); // "Red" "Blue"

✔ You can skip values:


const [first, , third] = colors;
console.log(third); // "Green"

12.5 Spread & Rest Operators (...)


The ... operator is powerful for working with arrays and objects.

1. Using Spread (...) with Arrays


const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4, 5];
console.log(newNumbers); // [1, 2, 3, 4, 5]

✔ Combines or copies arrays easily.

2. Using Spread (...) with Objects


const user = { name: "Dhanian", age: 25 };
const newUser = { ...user, country: "Kenya" };
console.log(newUser);
// { name: "Dhanian", age: 25, country: "Kenya" }

3. Using Rest (...) for Function Parameters


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

✔ The rest operator collects function arguments into an array.

12.6 Promises & Async/Await: Modern Asynchronous


JavaScript
🔹
🔹 Promises allow handling asynchronous operations.
async/await makes async code look synchronous.

1. Using Promises
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data loaded");
}, 2000);
});
};

fetchData().then(data => console.log(data)); // "Data loaded" after 2 seconds

2. Using async/await (Simpler than Promises)


const fetchData = async () => {
const data = await new Promise(resolve => setTimeout(() => resolve("Data
loaded"), 2000));
console.log(data);
};

fetchData();

✔ await pauses execution until the Promise resolves.

📌 Case Study: Using ES6+ Features in a Real-World Project


Let’s build a simple user data management system using ES6+:
const users = [
{ id: 1, name: "Dhanian", role: "Developer" },
{ id: 2, name: "Alice", role: "Designer" }
];

// Function to find a user


const getUser = async (id) => {
const user = users.find(user => user.id === id);
return user ? user : "User not found";
};

// Example Usage
getUser(1).then(user => console.log(user));

Summary & Next Steps


🎯 We covered essential ES6+ features:
✔ let & const
✔ Arrow Functions
✔ Template Literals
✔ Destructuring
✔ Spread/Rest Operator
✔ Promises & Async/Await

Chapter 13: JavaScript DOM Manipulation


(Making Webpages Interactive)
🚀
Now that we’ve covered modern JavaScript features (ES6+), let's move to an exciting topic—
DOM Manipulation!
The Document Object Model (DOM) allows JavaScript to dynamically update HTML and CSS


to create interactive web applications.



Modify HTML elements
Change CSS styles dynamically

✅ Handle user events (clicks, keyboard input, etc.)


Add or remove elements from the page

🎯
By the end of this chapter, you'll be able to build interactive, dynamic webpages with
JavaScript!

13.1 What is the DOM?


The DOM (Document Object Model) represents an HTML page as a tree-like structure.
Each element (<div>, <p>, <button>, etc.) is a node in this tree, and JavaScript can interact

🔹
with them.
Example HTML Page:
<!DOCTYPE html>
<html lang="en">
<head>
<title>DOM Example</title>
</head>
<body>
<h1 id="title">Hello, World!</h1>
<button onclick="changeText()">Click Me</button>
<script>
function changeText() {
document.getElementById("title").innerText = "You Clicked the Button!";
}
</script>
</body>
</html>

✔ When the button is clicked, JavaScript updates the <h1> content dynamically.

13.2 Selecting Elements in the DOM


To manipulate elements, we need to select them first. Here are different ways to do it:

1. getElementById() (Select by ID)


const title = document.getElementById("title");
console.log(title.innerText); // "Hello, World!"

✔ Returns the first element with the given id.

2. getElementsByClassName() (Select by Class)


const items = document.getElementsByClassName("item");
console.log(items[0]); // First element with class "item"

✔ Returns a collection (array-like) of elements.

3. getElementsByTagName() (Select by Tag Name)


const paragraphs = document.getElementsByTagName("p");
console.log(paragraphs[0]); // First <p> element

✔ Returns all elements of the given tag name.

4. querySelector() (Select First Matching Element)


const heading = document.querySelector("h1");
console.log(heading.innerText); // "Hello, World!"

✔ Returns the first matching element.

5. querySelectorAll() (Select All Matching Elements)


const allItems = document.querySelectorAll(".item");
console.log(allItems.length); // Total elements with class "item"

✔ Returns a NodeList (like an array).


13.3 Modifying Elements with JavaScript
Once we select an element, we can modify its content, attributes, or styles.

1. Changing Text Content (innerText & innerHTML)


document.getElementById("title").innerText = "New Title";
document.getElementById("title").innerHTML = "<em>New Title</em>"; // Supports
HTML

2. Changing Attributes (setAttribute() & getAttribute())


const link = document.querySelector("a");
link.setAttribute("href", "https://google.com");
console.log(link.getAttribute("href")); // "https://google.com"

✔ Use .setAttribute() to modify attributes like href, src, etc.

3. Changing Styles (style Property)


const title = document.getElementById("title");
title.style.color = "blue";
title.style.fontSize = "24px";

✔ You can modify CSS properties dynamically.

13.4 Event Listeners: Handling User Events


JavaScript can respond to user interactions like clicks, typing, hovering, etc.

1. Handling Click Events


const button = document.querySelector("button");

button.addEventListener("click", () => {
alert("Button Clicked!");
});

✔ .addEventListener("click", function) attaches a click event handler.

2. Handling Mouse Events (mouseover, mouseout)


const box = document.querySelector(".box");

box.addEventListener("mouseover", () => {
box.style.backgroundColor = "yellow";
});

box.addEventListener("mouseout", () => {
box.style.backgroundColor = "white";
});

✔ mouseover triggers when the mouse enters.


✔ mouseout triggers when the mouse leaves.

3. Handling Keyboard Events (keydown, keyup)


document.addEventListener("keydown", (event) => {
console.log(`Key Pressed: ${event.key}`);
});

✔ Logs the key pressed on the keyboard.

13.5 Creating & Removing Elements Dynamically


JavaScript allows creating new elements and removing existing ones dynamically.

1. Creating New Elements (createElement())


const newParagraph = document.createElement("p");
newParagraph.innerText = "This is a new paragraph!";
document.body.appendChild(newParagraph);

✔ .appendChild() adds the new element inside the <body>.

2. Removing Elements (remove())


const title = document.getElementById("title");
title.remove();

✔ .remove() deletes an element from the DOM.

13.6 Real-World Project: Interactive To-Do List


Let’s build a simple to-do list where users can add and remove tasks dynamically.

📌 HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<title>To-Do List</title>
</head>
<body>
<h1>My To-Do List</h1>
<input type="text" id="taskInput" placeholder="Enter task">
<button id="addTask">Add Task</button>
<ul id="taskList"></ul>

<script src="script.js"></script>
</body>
</html>

📌 JavaScript Logic (script.js)


const input = document.getElementById("taskInput");
const button = document.getElementById("addTask");
const list = document.getElementById("taskList");

button.addEventListener("click", () => {
const taskText = input.value;

if (taskText === "") return;

const listItem = document.createElement("li");


listItem.innerText = taskText;


const deleteButton = document.createElement("button");
deleteButton.innerText = " ";
deleteButton.addEventListener("click", () => listItem.remove());

listItem.appendChild(deleteButton);
list.appendChild(listItem);

input.value = ""; // Clear input field


});

13.7 Summary & Next Steps


🎯 In this chapter, we learned:
✔ How to select elements (getElementById(), querySelector(), etc.)
✔ How to modify elements (text, attributes, styles)
✔ How to handle events (click, keyboard, mouse)
✔ How to create and remove elements dynamically
✔ Built a To-Do List project using JavaScript
Chapter 14: JavaScript Local Storage (Saving
User Data Permanently)
In the previous chapter, we learned how to dynamically manipulate the DOM and build an

😲
interactive To-Do List using JavaScript. However, there was one issue—when the page refreshed,

🔹
the tasks disappeared!

🔹 How can we save user data even after refreshing the page?
How can we create web apps that remember user preferences?
The answer: Local Storage!

14.1 What is Local Storage?


Local Storage is a browser feature that allows us to store data permanently (until manually
deleted). Unlike cookies, local storage doesn’t expire and can hold up to 5MB of data.
✔ Stores data in key-value pairs
✔ Persists even after page refresh or closing the browser
✔ Only accessible from JavaScript on the same domain

14.2 How to Use Local Storage in JavaScript


Local Storage has three main functions:

Function Description
localStorage.setItem(key, value) Stores data
localStorage.getItem(key) Retrieves data
localStorage.removeItem(key) Deletes data

1. Storing Data in Local Storage


localStorage.setItem("username", "Dhanian");

✔ Stores "Dhanian" under the key "username".

2. Retrieving Data from Local Storage


const user = localStorage.getItem("username");
console.log(user); // Output: "Dhanian"

✔ Retrieves and prints "Dhanian".


3. Removing Data from Local Storage
localStorage.removeItem("username");

✔ Deletes the "username" data.

4. Clearing All Data in Local Storage


localStorage.clear();

✔ Removes all stored data.

14.3 Storing Complex Data (Arrays & Objects)

🔹
By default, local storage only stores strings.

🔹 How do we store arrays or objects?


Solution: Convert them to JSON format using JSON.stringify().

1. Storing an Array in Local Storage


const fruits = ["Apple", "Banana", "Orange"];

localStorage.setItem("myFruits", JSON.stringify(fruits));

✔ JSON.stringify() converts the array to a string.

2. Retrieving and Parsing an Array


const storedFruits = JSON.parse(localStorage.getItem("myFruits"));

console.log(storedFruits); // ["Apple", "Banana", "Orange"]

✔ JSON.parse() converts the stored string back to an array.

3. Storing Objects in Local Storage


const user = {
name: "Dhanian",
age: 25,
country: "Kenya"
};

localStorage.setItem("userData", JSON.stringify(user));

✔ Stores an object as a string.


4. Retrieving and Parsing an Object
const storedUser = JSON.parse(localStorage.getItem("userData"));

console.log(storedUser.name); // "Dhanian"
console.log(storedUser.age); // 25

✔ Retrieves and converts the object back to its original form.

14.4 Practical Project: To-Do List with Local Storage


Now, let’s upgrade our To-Do List from Chapter 13 so that tasks remain saved even after
refreshing the page.

📌 Updated HTML Structure


<!DOCTYPE html>
<html lang="en">
<head>
<title>Persistent To-Do List</title>
</head>
<body>
<h1>My To-Do List</h1>
<input type="text" id="taskInput" placeholder="Enter task">
<button id="addTask">Add Task</button>
<ul id="taskList"></ul>

<script src="script.js"></script>
</body>
</html>

📌 JavaScript (script.js) with Local Storage


const input = document.getElementById("taskInput");
const button = document.getElementById("addTask");
const list = document.getElementById("taskList");

// Load tasks from Local Storage when page loads


document.addEventListener("DOMContentLoaded", loadTasks);

button.addEventListener("click", () => {
const taskText = input.value;

if (taskText === "") return;

addTask(taskText);
saveTaskToLocalStorage(taskText);

input.value = ""; // Clear input field


});

function addTask(taskText) {
const listItem = document.createElement("li");
listItem.innerText = taskText;
deleteButton.innerText = " "; ❌
const deleteButton = document.createElement("button");

deleteButton.addEventListener("click", () => {
listItem.remove();
removeTaskFromLocalStorage(taskText);
});

listItem.appendChild(deleteButton);
list.appendChild(listItem);
}

function saveTaskToLocalStorage(taskText) {
let tasks = JSON.parse(localStorage.getItem("tasks")) || [];
tasks.push(taskText);
localStorage.setItem("tasks", JSON.stringify(tasks));
}

function loadTasks() {
let tasks = JSON.parse(localStorage.getItem("tasks")) || [];
tasks.forEach(task => addTask(task));
}

function removeTaskFromLocalStorage(taskText) {
let tasks = JSON.parse(localStorage.getItem("tasks")) || [];
tasks = tasks.filter(task => task !== taskText);
localStorage.setItem("tasks", JSON.stringify(tasks));
}

14.5 Summary & Next Steps


🎯 In this chapter, we learned:
✔ What Local Storage is and how it works
✔ How to store and retrieve data using localStorage.setItem() and
localStorage.getItem()
✔ How to store arrays and objects using JSON.stringify() and JSON.parse()
✔ How to create a To-Do List that saves tasks permanently

Chapter 15: JavaScript Session Storage &


Cookies (Tracking User Sessions)
In the last chapter, we learned how Local Storage allows us to store data permanently. But what if

🔹
we need data to last only for a single session—meaning it disappears when the browser is closed?

🔹 For temporary storage, we use Session Storage.

🚀
For tracking users over time, we use Cookies.
Let’s explore both in detail!
15.1 What is Session Storage?
Session Storage is similar to Local Storage, but the key difference is:
✔ Data persists only as long as the browser tab is open.
✔ Once the tab is closed, all data is erased.
✔ Data is not shared across tabs.

15.2 How to Use Session Storage


Session Storage has three main functions:

Function Description
sessionStorage.setItem(key, value) Stores data
sessionStorage.getItem(key) Retrieves data
sessionStorage.removeItem(key) Deletes data

1. Storing Data in Session Storage


sessionStorage.setItem("user", "Dhanian");

✔ Saves "Dhanian" under the "user" key.

2. Retrieving Data from Session Storage


const user = sessionStorage.getItem("user");
console.log(user); // Output: "Dhanian"

✔ Retrieves "Dhanian".

3. Removing Data from Session Storage


sessionStorage.removeItem("user");

✔ Deletes the "user" key.

4. Clearing All Data in Session Storage


sessionStorage.clear();

✔ Deletes all stored data.


15.3 Storing Objects in Session Storage
Like Local Storage, Session Storage only stores strings. So we use JSON.stringify() and
JSON.parse() for arrays & objects.

1. Storing an Object
const user = { name: "Dhanian", age: 25, country: "Kenya" };

sessionStorage.setItem("userData", JSON.stringify(user));

✔ Converts the object to a string before storing.

2. Retrieving and Parsing an Object


const storedUser = JSON.parse(sessionStorage.getItem("userData"));

console.log(storedUser.name); // "Dhanian"
console.log(storedUser.age); // 25

✔ Retrieves and converts it back into an object.

15.4 What are Cookies? 🍪


A cookie is a small data file stored on the user's browser that helps websites remember user
preferences, login sessions, and more.
✔ Data can persist across browser sessions
✔ Has an expiration date (unlike Local/Session Storage)
✔ Used for authentication, tracking, and analytics

15.5 Creating and Using Cookies in JavaScript


JavaScript has a built-in document.cookie property to manage cookies.

1. Creating a Cookie
document.cookie = "username=Dhanian";

✔ Creates a cookie storing "Dhanian" under "username".

2. Setting an Expiry Date for a Cookie


document.cookie = "username=Dhanian; expires=Fri, 1 Mar 2025 12:00:00 UTC";
✔ Cookie expires on March 1, 2025.

3. Reading a Cookie
console.log(document.cookie);

✔ Outputs: "username=Dhanian"

4. Updating a Cookie
document.cookie = "username=DhanianCode; expires=Fri, 1 Mar 2025 12:00:00 UTC";

✔ Overwrites the old "username" cookie.

5. Deleting a Cookie
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC";

✔ Sets an expired date, deleting the cookie.

15.6 Practical Project: Dark Mode with Cookies


Let’s create a simple dark mode toggle that remembers the user’s preference using cookies.

📌 HTML: Dark Mode Toggle


<!DOCTYPE html>
<html lang="en">
<head>
<title>Dark Mode with Cookies</title>
<style>
body.dark { background-color: black; color: white; }
</style>
</head>
<body>
<button id="toggleMode">Toggle Dark Mode</button>
<script src="script.js"></script>
</body>
</html>

📌 JavaScript: Save Theme in Cookies


const button = document.getElementById("toggleMode");

// Load saved mode from cookies


document.addEventListener("DOMContentLoaded", () => {
const savedMode = getCookie("theme");
if (savedMode === "dark") {
document.body.classList.add("dark");
}
});

button.addEventListener("click", () => {
document.body.classList.toggle("dark");
const mode = document.body.classList.contains("dark") ? "dark" : "light";
setCookie("theme", mode, 7);
});

// Function to set a cookie


function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
document.cookie = `${name}=${value}; expires=${date.toUTCString()}; path=/`;
}

// Function to get a cookie


function getCookie(name) {
const cookies = document.cookie.split("; ");
for (let cookie of cookies) {
let [key, value] = cookie.split("=");
if (key === name) return value;
}
return "";
}

✔ Now, the dark mode preference is saved, even after refreshing the page! 🎉
15.7 Summary & Next Steps
🎯 In this chapter, we learned:
✔ Session Storage stores temporary data only while the tab is open
✔ Cookies store persistent data and support expiration dates
✔ How to create a Dark Mode feature that remembers user preferences using cookies

Chapter 15: JavaScript Session Storage &


Cookies (Tracking User Sessions)
In the last chapter, we learned how Local Storage allows us to store data permanently. But what if

🔹
we need data to last only for a single session—meaning it disappears when the browser is closed?

🔹 For temporary storage, we use Session Storage.

🚀
For tracking users over time, we use Cookies.
Let’s explore both in detail!
15.1 What is Session Storage?
Session Storage is similar to Local Storage, but the key difference is:
✔ Data persists only as long as the browser tab is open.
✔ Once the tab is closed, all data is erased.
✔ Data is not shared across tabs.

15.2 How to Use Session Storage


Session Storage has three main functions:

Function Description
sessionStorage.setItem(key, value) Stores data
sessionStorage.getItem(key) Retrieves data
sessionStorage.removeItem(key) Deletes data

1. Storing Data in Session Storage


sessionStorage.setItem("user", "Dhanian");

✔ Saves "Dhanian" under the "user" key.

2. Retrieving Data from Session Storage


const user = sessionStorage.getItem("user");
console.log(user); // Output: "Dhanian"

✔ Retrieves "Dhanian".

3. Removing Data from Session Storage


sessionStorage.removeItem("user");

✔ Deletes the "user" key.

4. Clearing All Data in Session Storage


sessionStorage.clear();

✔ Deletes all stored data.


15.3 Storing Objects in Session Storage
Like Local Storage, Session Storage only stores strings. So we use JSON.stringify() and
JSON.parse() for arrays & objects.

1. Storing an Object
const user = { name: "Dhanian", age: 25, country: "Kenya" };

sessionStorage.setItem("userData", JSON.stringify(user));

✔ Converts the object to a string before storing.

2. Retrieving and Parsing an Object


const storedUser = JSON.parse(sessionStorage.getItem("userData"));

console.log(storedUser.name); // "Dhanian"
console.log(storedUser.age); // 25

✔ Retrieves and converts it back into an object.

15.4 What are Cookies? 🍪


A cookie is a small data file stored on the user's browser that helps websites remember user
preferences, login sessions, and more.
✔ Data can persist across browser sessions
✔ Has an expiration date (unlike Local/Session Storage)
✔ Used for authentication, tracking, and analytics

15.5 Creating and Using Cookies in JavaScript


JavaScript has a built-in document.cookie property to manage cookies.

1. Creating a Cookie
document.cookie = "username=Dhanian";

✔ Creates a cookie storing "Dhanian" under "username".

2. Setting an Expiry Date for a Cookie


document.cookie = "username=Dhanian; expires=Fri, 1 Mar 2025 12:00:00 UTC";
✔ Cookie expires on March 1, 2025.

3. Reading a Cookie
console.log(document.cookie);

✔ Outputs: "username=Dhanian"

4. Updating a Cookie
document.cookie = "username=DhanianCode; expires=Fri, 1 Mar 2025 12:00:00 UTC";

✔ Overwrites the old "username" cookie.

5. Deleting a Cookie
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC";

✔ Sets an expired date, deleting the cookie.

15.6 Practical Project: Dark Mode with Cookies


Let’s create a simple dark mode toggle that remembers the user’s preference using cookies.

📌 HTML: Dark Mode Toggle


<!DOCTYPE html>
<html lang="en">
<head>
<title>Dark Mode with Cookies</title>
<style>
body.dark { background-color: black; color: white; }
</style>
</head>
<body>
<button id="toggleMode">Toggle Dark Mode</button>
<script src="script.js"></script>
</body>
</html>

📌 JavaScript: Save Theme in Cookies


const button = document.getElementById("toggleMode");

// Load saved mode from cookies


document.addEventListener("DOMContentLoaded", () => {
const savedMode = getCookie("theme");
if (savedMode === "dark") {
document.body.classList.add("dark");
}
});

button.addEventListener("click", () => {
document.body.classList.toggle("dark");
const mode = document.body.classList.contains("dark") ? "dark" : "light";
setCookie("theme", mode, 7);
});

// Function to set a cookie


function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
document.cookie = `${name}=${value}; expires=${date.toUTCString()}; path=/`;
}

// Function to get a cookie


function getCookie(name) {
const cookies = document.cookie.split("; ");
for (let cookie of cookies) {
let [key, value] = cookie.split("=");
if (key === name) return value;
}
return "";
}

✔ Now, the dark mode preference is saved, even after refreshing the page! 🎉
15.7 Summary & Next Steps
🎯 In this chapter, we learned:
✔ Session Storage stores temporary data only while the tab is open
✔ Cookies store persistent data and support expiration dates
✔ How to create a Dark Mode feature that remembers user preferences using cookies

Chapter 16: JavaScript Fetch API & Making


HTTP Requests
The Fetch API is one of the most powerful features in JavaScript. It allows us to communicate

📌
with web servers, fetch data, and send requests.
Use Cases:
✔ Fetching data from an API (e.g., weather, stock prices)
✔ Sending form data to a server
✔ Handling CRUD operations (Create, Read, Update, Delete)
16.1 What is the Fetch API?
The Fetch API provides a modern way to make HTTP requests. It replaces the older
XMLHttpRequest method and is:

✔ Simpler and cleaner to use


✔ Promise-based, making it more readable

💡
✔ Supports JSON parsing out of the box
Basic Fetch Syntax:
fetch(url, options)
.then(response => response.json()) // Convert response to JSON
.then(data => console.log(data)) // Handle the data
.catch(error => console.error("Error:", error)); // Handle errors

16.2 Making a GET Request


A GET request is used to retrieve data from a server.
Let’s fetch random user data from an API.
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json()) // Convert response to JSON
.then(users => console.log(users)) // Log the users
.catch(error => console.error("Error:", error));

✔ The fetch() function sends a request to the API.


✔ .then(response => response.json()) converts the response to JSON.
✔ .then(users => console.log(users)) logs the fetched user data.
✔ .catch(error => console.error("Error:", error)) handles errors.

16.3 Handling Errors in Fetch Requests


What if the API fails? We need to handle errors properly.
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP Error! Status: ${response.status}`);
}
return response.json();
})
.then(users => console.log(users))
.catch(error => console.error("Fetch Error:", error));

✔ response.ok checks if the request was successful.


✔ throw new Error(...) catches failed requests.
16.4 Making a POST Request (Sending Data)
A POST request is used to send data to the server.
Let’s create a new user.
const newUser = {
name: "Dhanian",
email: "dhanian@example.com",
username: "dhanianDev"
};

fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(newUser)
})
.then(response => response.json())
.then(data => console.log("User created:", data))
.catch(error => console.error("Error:", error));

✔ method: "POST" tells the server to create new data.


✔ headers: { "Content-Type": "application/json" } specifies JSON format.
✔ body: JSON.stringify(newUser) sends the data as JSON.

16.5 Making a PUT Request (Updating Data)


A PUT request updates an existing record.
const updatedUser = {
name: "Dhanian Updated",
email: "dhanian_updated@example.com"
};

fetch("https://jsonplaceholder.typicode.com/users/1", {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(updatedUser)
})
.then(response => response.json())
.then(data => console.log("User updated:", data))
.catch(error => console.error("Error:", error));

✔ PUT replaces all data of an existing user.

16.6 Making a DELETE Request (Removing Data)


A DELETE request removes a record from the server.
fetch("https://jsonplaceholder.typicode.com/users/1", {
method: "DELETE"
})
.then(response => {
if (response.ok) {
console.log("User deleted successfully");
} else {
console.error("Failed to delete user");
}
})
.catch(error => console.error("Error:", error));

✔ If response.ok, the user is deleted.

16.7 Using Async/Await with Fetch


Using .then() is great, but async/await makes Fetch cleaner!

Here’s how to fetch user data with async/await:


async function getUsers() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error(`HTTP Error! Status: ${response.status}`);
}
const users = await response.json();
console.log(users);
} catch (error) {
console.error("Error:", error);
}
}

getUsers();

✔ await fetch(url) waits for the response.


✔ await response.json() waits for JSON conversion.
✔ try...catch handles errors.

16.8 Real-World Project: Weather App


Let’s create a Weather App that fetches live weather data!
🌦
📌 HTML: Basic UI
<!DOCTYPE html>
<html lang="en">
<head>
<title>Weather App</title>
</head>
<body>
<input type="text" id="city" placeholder="Enter city name">
<button id="getWeather">Get Weather</button>
<p id="weatherResult"></p>
<script src="script.js"></script>
</body>
</html>

📌 JavaScript: Fetching Weather Data


document.getElementById("getWeather").addEventListener("click", async () => {
const city = document.getElementById("city").value;
if (!city) {
alert("Please enter a city name");
return;
}

const apiKey = "YOUR_OPENWEATHER_API_KEY"; // Replace with your API key


const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=$
{apiKey}&units=metric`;

try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("City not found");
}

const data = await response.json();


document.getElementById("weatherResult").innerText =
`Temperature in ${data.name}: ${data.main.temp}°C`;
} catch (error) {
console.error("Error:", error);
document.getElementById("weatherResult").innerText = "Error fetching weather
data";
}
});

✔ Fetches live weather data using an API.


✔ Displays the temperature of the entered city.

16.9 Summary & Next Steps


🎯 In this chapter, we learned:
✔ How to make GET, POST, PUT, and DELETE requests
✔ How to handle errors in Fetch requests
✔ How to use async/await for cleaner code
✔ How to build a Weather App using the Fetch API

Chapter 17: JavaScript Promises &


Asynchronous Programming
JavaScript is single-threaded, meaning it can execute only one task at a time. However, modern
applications require handling asynchronous operations, like:
✔ Fetching data from APIs
✔ Reading/writing files
✔ Handling user interactions
To manage these tasks efficiently, JavaScript provides Promises and async/await.

17.1 What is a Promise?


A Promise is an object that represents a task that may complete in the future. It can be in one of
these states:
1️⃣Pending - The operation has started but is not finished.
2️⃣Fulfilled - The operation completed successfully.
3️⃣Rejected - The operation failed.
💡 Basic Promise Syntax:
const myPromise = new Promise((resolve, reject) => {
let success = true;

setTimeout(() => {
if (success) {
resolve("Operation was successful!");
} else {
reject("Operation failed!");
}
}, 2000);
});

myPromise
.then(result => console.log(result)) // Runs if successful
.catch(error => console.error(error)); // Runs if failed

✔ resolve(value) fulfills the promise.


✔ reject(error) rejects the promise.

17.2 Using Promises in Real-World Examples


1️⃣ Fetching API Data with Promises
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(data => console.log("Post:", data))
.catch(error => console.error("Error:", error));

✔ fetch() returns a Promise.


✔ .then() processes the data when available.
✔ .catch() handles errors.
17.3 Chaining Promises
Sometimes, we need to run multiple tasks in sequence. Instead of nesting callbacks (callback
hell), we use Promise chaining.
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(post => {
console.log("Post:", post);
return fetch(`https://jsonplaceholder.typicode.com/users/${post.userId}`);
})
.then(response => response.json())
.then(user => console.log("User:", user))
.catch(error => console.error("Error:", error));

✔ Fetches a post, then fetches its author.

17.4 Async/Await: A Better Way to Handle Promises

💡
Async/Await makes working with Promises easier and code more readable.
Using async/await to fetch data:
async function getPostAndUser() {
try {
const postResponse = await
fetch("https://jsonplaceholder.typicode.com/posts/1");
const post = await postResponse.json();
console.log("Post:", post);

const userResponse = await


fetch(`https://jsonplaceholder.typicode.com/users/${post.userId}`);
const user = await userResponse.json();
console.log("User:", user);
} catch (error) {
console.error("Error:", error);
}
}

getPostAndUser();

✔ async makes the function asynchronous.


✔ await pauses execution until the Promise resolves.
✔ try...catch handles errors gracefully.

17.5 Handling Multiple Promises with Promise.all()


If you need to run multiple asynchronous tasks at the same time, Promise.all() can help.
const postPromise =
fetch("https://jsonplaceholder.typicode.com/posts/1").then(res => res.json());
const userPromise =
fetch("https://jsonplaceholder.typicode.com/users/1").then(res => res.json());

Promise.all([postPromise, userPromise])
.then(([post, user]) => {
console.log("Post:", post);
console.log("User:", user);
})
.catch(error => console.error("Error:", error));

✔ Fetches post & user at the same time.


✔ Promise.all([]) waits for all promises to complete.

17.6 Real-World Project: To-Do List with Async Storage


📌 HTML: To-Do List UI
<!DOCTYPE html>
<html lang="en">
<head>
<title>To-Do List</title>
</head>
<body>
<input type="text" id="task" placeholder="Enter task">
<button id="addTask">Add Task</button>
<ul id="taskList"></ul>
<script src="script.js"></script>
</body>
</html>

📌 JavaScript: Async Storage for Tasks


document.getElementById("addTask").addEventListener("click", async () => {
const taskInput = document.getElementById("task");
const task = taskInput.value;

if (!task) {
alert("Enter a task!");
return;
}

let tasks = JSON.parse(localStorage.getItem("tasks")) || [];


tasks.push(task);
localStorage.setItem("tasks", JSON.stringify(tasks));

await displayTasks();
taskInput.value = "";
});

async function displayTasks() {


const taskList = document.getElementById("taskList");
taskList.innerHTML = "";

let tasks = JSON.parse(localStorage.getItem("tasks")) || [];

tasks.forEach(task => {
const li = document.createElement("li");
li.innerText = task;
taskList.appendChild(li);
});
}
displayTasks();

✔ Stores tasks in localStorage.


✔ Uses async function to display tasks.

17.7 Summary & Next Steps


🎯 In this chapter, we learned:
✔ What Promises are and how they work.
✔ How to use fetch() with Promises.
✔ How to use async/await for cleaner code.
✔ How to handle multiple Promises with Promise.all().
✔ Built a To-Do List with async storage.

Chapter 18: JavaScript Closures & Scope


Closures and scope are fundamental concepts in JavaScript that impact variable access, memory
management, and function execution. Mastering them is essential for writing efficient and bug-
free JavaScript code.

18.1 Understanding Scope in JavaScript


Scope determines the accessibility of variables in different parts of your code. JavaScript has the
following types of scope:

1️⃣ Global Scope


A variable declared outside any function or block is global and can be accessed anywhere in the
script.
let globalVar = "I'm a global variable!";

function showGlobal() {

}
console.log(globalVar); // ✅ Accessible inside the function

showGlobal();
console.log(globalVar); // ✅ Accessible anywhere

✔ Global variables stay in memory as long as the program runs.


✔ Avoid excessive global variables to prevent conflicts.
2️⃣ Function Scope
Variables declared inside a function are only accessible within that function.
function greet() {
let name = "Dhanian";
console.log("Hello, " + name);
}

greet();
console.log(name); // ❌ Error! "name" is not defined outside the function

✔ Function-scoped variables cannot be accessed outside the function.


✔ This helps encapsulate data and avoid conflicts.

3️⃣ Block Scope (let and const)


Variables declared with let and const are limited to the block they are defined in.
if (true) {

}
let message = "Inside block";
console.log(message); // ✅
Accessible here

console.log(message); // ❌ Error! "message" is not accessible outside

✔ var ignores block scope, while let and const respect it.
✔ Always use let or const instead of var for better scoping.

18.2 What is a Closure?


A closure is a function that remembers the variables from its outer scope even after the outer
function has finished executing.

Example: Simple Closure


function outer() {
let count = 0;

return function inner() {


count++;
console.log("Count:", count);
};
}

const counter = outer();


counter(); // Count: 1
counter(); // Count: 2
counter(); // Count: 3

✔ The inner function (inner()) keeps access to count even after outer() has finished
running.
✔ Closures are powerful for maintaining state in JavaScript.
18.3 Real-World Uses of Closures
1️⃣ Private Variables (Encapsulation)
Closures help protect variables from being accessed directly.
function createCounter() {
let count = 0; // Private variable

return {
increment: function () {
count++;
console.log("Counter:", count);
},
decrement: function () {
count--;
console.log("Counter:", count);
},
getCount: function () {
return count;
},
};
}

const counter = createCounter();


counter.increment(); // Counter: 1

❌ ✅
counter.increment(); // Counter: 2
console.log(counter.getCount()); // 2
console.log(counter.count); // Undefined! "count" is private

✔ count is private and cannot be modified directly.


✔ Useful for secure and modular programming.

2️⃣ Event Listeners with Closures


Closures help preserve values in event handlers.
function handleClick() {
let count = 0;

return function () {
count++;
console.log("Button clicked:", count, "times");
};
}

const button = document.getElementById("myButton");


button.addEventListener("click", handleClick());

✔ The function remembers count even after execution.

3️⃣ Delaying Execution (setTimeout with Closures)


Closures allow delayed execution while keeping variables accessible.
function delayedMessage(msg, delay) {
setTimeout(() => {
console.log(msg);
}, delay);
}

delayedMessage("Hello after 2 seconds!", 2000);

✔ The message remembers msg even after delayedMessage() completes.

18.4 Practical Project: Countdown Timer with Closures


📌 HTML: Countdown Timer UI
<!DOCTYPE html>
<html lang="en">
<head>
<title>Countdown Timer</title>
</head>
<body>
<button id="startTimer">Start Timer</button>
<p id="timerDisplay">10</p>
<script src="script.js"></script>
</body>
</html>

📌 JavaScript: Countdown Timer Logic


function countdown(start) {
let time = start;

return function () {
if (time >= 0) {
document.getElementById("timerDisplay").innerText = time;
time--;
} else {
clearInterval(timer);
}
};
}

const timerFunction = countdown(10);


const timer = setInterval(timerFunction, 1000);

✔ Uses closures to remember the countdown value.


✔ Updates UI every second using setInterval().

18.5 Summary & Next Steps


🎯 In this chapter, we learned:
✔ Scope (Global, Function, Block).
✔ Closures and how they retain variables after function execution.
✔ Real-world uses (private variables, event listeners, timeouts).
✔ Built a countdown timer using closures.

Chapter 19: JavaScript Modules & Code


Organization
As JavaScript applications grow in complexity, organizing code efficiently becomes essential.
JavaScript modules help break code into manageable, reusable pieces for better maintainability,
readability, and scalability.

19.1 What Are JavaScript Modules?


A module is a self-contained piece of code that can be imported and exported between different
files. This helps keep code organized and reusable.

Why Use Modules?


✔ Encapsulation – Prevents global scope pollution.
✔ Reusability – Write code once and use it multiple times.
✔ Maintainability – Easier debugging and updates.
✔ Better Performance – Load only the required code when needed.

19.2 Using ES6 Modules (import & export)


ES6 introduced the import and export keywords to create modules. Let’s see how they work.

1️⃣ Exporting a Module


Save this in mathUtils.js:
// mathUtils.js
export function add(a, b) {
return a + b;
}

export function subtract(a, b) {


return a - b;
}

export const PI = 3.14159;


2️⃣ Importing a Module
Now, in app.js, import and use the functions from mathUtils.js:
// app.js
import { add, subtract, PI } from "./mathUtils.js";

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


console.log(subtract(10, 4)); // Output: 6
console.log(PI); // Output: 3.14159

✔ The import statement allows us to access functions and constants from mathUtils.js.

19.3 Default Exports


A module can have one default export, which can be imported without curly braces {}.

1️⃣ Creating a Default Export


// logger.js
export default function logMessage(message) {
console.log("Log:", message);
}

2️⃣ Importing a Default Export


// app.js
import logMessage from "./logger.js";

logMessage("Hello, JavaScript Modules!"); // Output: Log: Hello, JavaScript


Modules!

✔ Default exports don't require curly braces during import.


✔ You can rename the function while importing.
import myLogger from "./logger.js";
myLogger("This works too!"); // Output: Log: This works too!

19.4 Named vs. Default Exports


Default Export (export
Feature Named Export (export {})
default)
Number of Exports Multiple Only One
import { func } from import func from
Import Syntax

✅ ❌
"file.js"; "file.js";
Curly Braces
Yes No
Needed?
Best for Multiple functions/constants Single main function/class
19.5 Dynamic Imports (import())
Dynamic imports allow importing modules only when needed, improving performance.
function loadMathUtils() {
import("./mathUtils.js").then((module) => {
console.log(module.add(10, 5)); // Output: 15
});
}

loadMathUtils(); // Loads module only when this function is called

✔ Useful for lazy loading and code splitting.

19.6 Organizing JavaScript Code with Modules


For larger projects, structure your files in meaningful folders and modules:
/myProject
/modules
mathUtils.js
logger.js
app.js
index.html

✔ Keep functions related to one task in the same module.


✔ Use meaningful file names for better understanding.

19.7 Practical Project: Modular To-Do List App


We’ll create a modular to-do list with separate files for logic, UI handling, and storage.

📌 1. Folder Structure
/todoApp
/modules
storage.js
ui.js
app.js
index.html

📌 2. HTML: Basic To-Do List UI


<!DOCTYPE html>
<html lang="en">
<head>
<title>To-Do List</title>
</head>
<body>
<h2>To-Do List</h2>
<input type="text" id="taskInput" placeholder="Add a new task">
<button id="addTask">Add Task</button>
<ul id="taskList"></ul>
<script type="module" src="app.js"></script>
</body>
</html>

📌 3. JavaScript: Separate Logic into Modules


(1) UI Module (ui.js)
// ui.js
export function addTaskToUI(task) {
const list = document.getElementById("taskList");
const listItem = document.createElement("li");
listItem.textContent = task;
list.appendChild(listItem);
}

(2) Storage Module (storage.js)


// storage.js
export function saveTask(task) {
let tasks = JSON.parse(localStorage.getItem("tasks")) || [];
tasks.push(task);
localStorage.setItem("tasks", JSON.stringify(tasks));
}

export function getTasks() {


return JSON.parse(localStorage.getItem("tasks")) || [];
}

(3) Main App Logic (app.js)


// app.js
import { addTaskToUI } from "./modules/ui.js";
import { saveTask, getTasks } from "./modules/storage.js";

document.getElementById("addTask").addEventListener("click", () => {
const taskInput = document.getElementById("taskInput");
const task = taskInput.value.trim();

if (task) {
addTaskToUI(task);
saveTask(task);
taskInput.value = "";
}
});

// Load saved tasks on page load


window.addEventListener("DOMContentLoaded", () => {
getTasks().forEach(addTaskToUI);
});

✔ The app separates UI, storage, and logic into different modules.
✔ Local storage keeps tasks saved across sessions.
19.8 Summary & Next Steps
🎯 In this chapter, we learned:
✔ What modules are and why they’re useful.
✔ How to use import and export in JavaScript.
✔ The difference between named and default exports.
✔ How to use dynamic imports (import()).
✔ Built a modular To-Do List App with UI and storage separation.

Chapter 20: Asynchronous JavaScript &


Promises
Modern web applications rely heavily on asynchronous operations to handle API requests, user
interactions, and real-time updates. JavaScript provides several ways to manage asynchronous
code, including callbacks, Promises, and async/await.

20.1 Understanding Asynchronous JavaScript


What is Asynchronous Programming?
JavaScript is single-threaded, meaning it executes one operation at a time. However, some tasks
like fetching data from a server, reading a file, or waiting for a timer take time to complete.
Instead of blocking the execution, JavaScript uses asynchronous programming to handle such

📌
operations efficiently.
Example: Synchronous vs. Asynchronous Execution
console.log("Step 1: Start");

// Simulating an asynchronous task


setTimeout(() => {
console.log("Step 2: Asynchronous Task Completed");
}, 2000);

console.log("Step 3: End");

Output:
Step 1: Start
Step 3: End
Step 2: Asynchronous Task Completed (after 2 seconds)

✔ The setTimeout() function runs asynchronously, allowing other code to execute while
waiting.
20.2 Callbacks: The Old Way of Handling Asynchronous Code

📌
Before Promises, callbacks were used to handle asynchronous tasks.
Example: Callback Function
function fetchData(callback) {
setTimeout(() => {
callback("Data received");
}, 2000);
}

fetchData((data) => {
console.log(data);
});

Downside of Callbacks:
• If multiple callbacks are nested, it leads to Callback Hell, making code harder to read and
debug.

20.3 Promises: A Better Approach


A Promise is an object that represents a future value that might be available after an asynchronous
operation completes.
✔ States of a Promise:
• Pending – Initial state, operation is still in progress.
• Fulfilled – The operation was successful.

📌
• Rejected – The operation failed.
Example: Creating and Using a Promise
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received successfully!");
}, 2000);
});

fetchData
.then((message) => console.log(message))
.catch((error) => console.error(error));

✔ The .then() method runs when the Promise resolves successfully.


✔ The .catch() method handles errors or rejections.

20.4 Chaining Promises

📌
We can chain multiple .then() calls to handle dependent asynchronous tasks.

Example: Chaining Promises


function fetchUserData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "Dhanian" });
}, 2000);
});
}

function fetchUserPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 2000);
});
}

fetchUserData()
.then((user) => {
console.log("User:", user);
return fetchUserPosts(user.id);
})
.then((posts) => {
console.log("User Posts:", posts);
})
.catch((error) => console.error(error));

✔ First, we fetch the user data, then we fetch user posts using the user.id.

20.5 Async/Await: The Modern Way

📌
The async and await keywords simplify working with Promises, making code more readable.

Example: Using Async/Await


async function fetchData() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
let data = await response.json();
console.log(data);
} catch (error) {
console.error("Error fetching data:", error);
}
}

fetchData();

✔ await pauses execution until the Promise resolves.


✔ try...catch handles errors gracefully.

20.6 Real-World Example: Fetching Data from an API

📌
Let’s fetch user data from a public API.
Example: Fetching and Displaying User Information
async function getUserInfo() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/users/1");
let user = await response.json();
console.log(`Name: ${user.name}, Email: ${user.email}`);
} catch (error) {
console.error("Failed to fetch user data:", error);
}
}

getUserInfo();

✔ This fetches user data and displays the name and email.

20.7 Handling Multiple Asynchronous Calls


When multiple asynchronous tasks don't depend on each other, we can run them in parallel using

📌
Promise.all().

Example: Running Multiple API Calls in Parallel


async function fetchMultipleData() {
try {
let [user, posts] = await Promise.all([
fetch("https://jsonplaceholder.typicode.com/users/1").then((res) =>
res.json()
),
fetch("https://jsonplaceholder.typicode.com/posts?userId=1").then((res) =>
res.json()
),
]);

console.log("User:", user);
console.log("Posts:", posts);
} catch (error) {
console.error("Error fetching data:", error);
}
}

fetchMultipleData();

✔ Faster execution because both API calls run in parallel.

20.8 Project: Asynchronous Weather App


Let’s build a simple weather app that fetches data using an API.

📌 1. HTML Structure (index.html)


<!DOCTYPE html>
<html lang="en">
<head>
<title>Weather App</title>
</head>
<body>
<h2>Weather App</h2>
<input type="text" id="cityInput" placeholder="Enter city">
<button id="getWeather">Get Weather</button>
<p id="weatherResult"></p>

<script src="app.js"></script>
</body>
</html>

📌 2. JavaScript Code (app.js)


async function fetchWeather(city) {
try {
let response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}
&appid=YOUR_API_KEY&units=metric`
);
let data = await response.json();

if (data.cod !== 200) throw new Error(data.message);


return `Weather in ${data.name}: ${data.main.temp}°C`;
} catch (error) {
return `Error: ${error.message}`;
}
}

document.getElementById("getWeather").addEventListener("click", async () => {


let city = document.getElementById("cityInput").value.trim();
let result = await fetchWeather(city);
document.getElementById("weatherResult").textContent = result;
});

✔ Uses async/await to fetch weather data.


✔ Handles errors properly.

20.9 Summary & Next Steps


🎯 Key Takeaways:
✔ JavaScript is asynchronous and uses callbacks, Promises, and async/await.
✔ Promises make asynchronous code more readable and manageable.
✔ Async/Await simplifies working with Promises.
✔ Promise.all() runs multiple async operations in parallel.
✔ Built a weather app using API calls.

Chapter 21: The JavaScript Event Loop &


Concurrency
JavaScript is single-threaded, meaning it can execute only one task at a time. However, modern
web applications handle multiple operations simultaneously, such as fetching data, processing user
input, and updating the UI. This is possible due to the JavaScript Event Loop.
In this chapter, we will explore how JavaScript manages concurrency, including the Call Stack,
Web APIs, Task Queue, Microtasks, and the Event Loop.

21.1 Understanding the Call Stack

📌
The Call Stack is a data structure that keeps track of function execution.
Example: Call Stack in Action
function first() {
console.log("First function");
second();
}

function second() {
console.log("Second function");
third();
}

function third() {
console.log("Third function");
}

first();

✔ Execution Flow (Call Stack Behavior):


1. first() is called and added to the Call Stack.
2. Inside first(), second() is called and added to the stack.
3. Inside second(), third() is called and added to the stack.
4. third() executes and is removed from the stack.
5. second() finishes and is removed from the stack.
6. first() finishes and is removed from the stack.

✔ Output:
First function
Second function
Third function

21.2 Web APIs: Handling Asynchronous Operations


JavaScript uses Web APIs (provided by the browser) to handle asynchronous tasks like:
• setTimeout()
• fetch()

📌
• DOM events
Example: Using Web APIs
console.log("Start");

setTimeout(() => {
console.log("Inside setTimeout");
}, 2000);

console.log("End");

✔ Execution Flow:
1. "Start" is logged.
2. setTimeout() moves to the Web API environment.
3. "End" is logged immediately.
4. After 2 seconds, the callback function is added to the Task Queue and then executed.
✔ Output:
Start
End
Inside setTimeout (after 2 seconds)

21.3 The Event Loop: How JavaScript Manages Tasks


The Event Loop ensures that the Call Stack is never blocked by continuously checking:
• If the Call Stack is empty.

📌
• If there are pending tasks in the Task Queue or Microtask Queue.
Visual Representation of the Event Loop:
Call Stack | Web APIs | Task Queue
------------|-----------|------------
console.log("Start")
setTimeout() --> Sent to Web API
console.log("End")
------------|-----------|------------
Web API completes setTimeout() after 2 sec
Callback function added to Task Queue
Event Loop moves it to the Call Stack

✔ Example: Event Loop in Action


console.log("Start");

setTimeout(() => {
console.log("Inside setTimeout");
}, 0);

console.log("End");

✔ Output:
Start
End
Inside setTimeout

Even though setTimeout has 0 milliseconds delay, the callback is executed after "End"
because it goes through the Event Loop.
21.4 Microtask Queue vs. Task Queue
There are two types of queues in JavaScript:
1. Microtask Queue (Higher Priority)
• Promises (.then(), .catch())
• MutationObserver
2. Task Queue (Lower Priority)
• setTimeout(), setInterval()

📌
• Event Listeners
Example: Microtasks vs. Task Queue
console.log("Start");

setTimeout(() => {
console.log("setTimeout");
}, 0);

Promise.resolve().then(() => console.log("Promise"));

console.log("End");

✔ Output Order:
Start
End
Promise
setTimeout

✔ Why?
• The Promise callback runs first (Microtask Queue).
• The setTimeout callback runs after (Task Queue).

21.5 Real-World Example: Fetching Data with Event Loop

📌
Let's see how the Event Loop processes an API request.
Example: Fetching Data
console.log("Start");

fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => response.json())
.then(data => console.log("Data:", data));

console.log("End");

✔ Execution Flow:
1. "Start" is logged.
2. fetch() request is sent to the Web API.
3. "End" is logged immediately.
4. The API response returns and goes to the Microtask Queue.
5. The .then() function executes after the Call Stack is empty.

✔ Output Order:
Start
End
Data: {userId: 1, id: 1, title: "..."}

21.6 Performance Optimization with Event Loop


To improve JavaScript performance, follow these tips:
✔ Use Web Workers for Heavy Computations
• Web Workers allow JavaScript to run on a separate thread.
✔ Minimize Long-Running Tasks
• If a function takes too long, split it into smaller tasks.
✔ Use requestAnimationFrame() for UI Updates
• Avoid blocking UI by using requestAnimationFrame() instead of setTimeout().

✔ Prioritize Microtasks for Critical Updates


• Use Promises when possible instead of setTimeout().

21.7 Summary & Next Steps


🎯 Key Takeaways: ✔ The Call Stack processes function execution.
✔ Web APIs handle asynchronous operations.
✔ The Event Loop moves tasks from the Task Queue & Microtask Queue to the Call Stack.
✔ Microtasks (Promises) have higher priority than tasks like setTimeout().

Chapter 22: JavaScript Memory Management


& Garbage Collection
Efficient memory management is crucial for building high-performance applications. JavaScript
automatically manages memory using Garbage Collection (GC), but understanding how memory
is allocated and released helps developers write optimized and bug-free code.
In this chapter, we will explore:
• Memory allocation & lifecycle
• How Garbage Collection works
• Memory leaks and how to prevent them
• Optimizing memory usage
22.1 Understanding Memory Lifecycle in JavaScript
JavaScript uses automatic memory management, meaning developers don't manually allocate or
free memory. However, understanding how JavaScript handles memory can prevent performance
issues.

📌 Memory Lifecycle:
1. Allocation – Memory is reserved when variables, objects, or functions are created.
2. Usage – Memory is used for storing data and performing computations.
3. Release (Garbage Collection) – Unused memory is automatically reclaimed.

22.2 Memory Allocation in JavaScript


JavaScript allocates memory when:
• Declaring variables and constants
• Creating objects and arrays

📌
• Defining functions
Example: Memory Allocation
// Primitive type (stored in stack memory)
let name = "Dhanian"; // Allocates memory for the string

// Object type (stored in heap memory)


let user = {
name: "Dhanian",
age: 25
}; // Allocates memory for an object

// Array
let numbers = [1, 2, 3, 4, 5]; // Allocates memory for array elements

// Function
function greet() {
return "Hello, world!";
} // Function stored in memory

✔ Stack vs. Heap Memory


• Stack Memory: Stores primitive values (numbers, strings, booleans).
• Heap Memory: Stores objects, arrays, functions (complex structures).

22.3 How JavaScript Garbage Collection Works


JavaScript automatically frees up memory when it is no longer needed using Garbage Collection
(GC).
✔ How does GC work?
• JavaScript uses the Mark-and-Sweep algorithm.
• The Garbage Collector identifies "reachable" objects.

📌
• Unused (unreachable) objects are removed from memory.
Example: Garbage Collection in Action
let user = { name: "Dhanian" }; // Object allocated in heap
user = null; // No longer reachable, eligible for garbage collection

After user = null;, the object { name: "Dhanian" } is no longer referenced, so


JavaScript automatically frees up memory.

22.4 Common Memory Leaks in JavaScript


Even with Garbage Collection, memory leaks can occur when objects are unintentionally retained.

1️⃣ Unused Global Variables


Global variables stay in memory throughout the program.
Bad Practice:
function createData() {
globalData = new Array(1000000).fill("Memory Leak"); // No `let`, `const`, or
`var`
}
createData(); // Stays in memory forever!

✔ Solution: Use let or const to avoid unintentional global variables.

2️⃣ Forgotten Timers & Event Listeners


setInterval() and event listeners keep references to objects.

Memory Leak with setInterval()


function startTimer() {
setInterval(() => {
console.log("Running...");
}, 1000); // This runs forever!
}
startTimer();

✔ Solution: Clear intervals when they are no longer needed.


let timer = setInterval(() => {
console.log("Running...");
}, 1000);

// Clear the interval after 5 seconds


setTimeout(() => clearInterval(timer), 5000);
3️⃣ Retaining DOM References


Storing DOM elements in variables without removing them can cause leaks.
Memory Leak with Detached Elements
let button = document.getElementById("myButton");
button.addEventListener("click", () => {
console.log("Clicked!");
});

// Later, button is removed from DOM, but listener still exists!


document.body.removeChild(button);

✔ Solution: Remove event listeners before removing elements.


button.removeEventListener("click", () => {
console.log("Clicked!");
});
document.body.removeChild(button);

4️⃣ Closures Holding References


Closures can retain references to variables, preventing GC.
Memory Leak with Closures
function outer() {
let bigData = new Array(1000000).fill("Memory Leak");
return function inner() {
console.log(bigData.length);
};
}
let leak = outer(); // `bigData` is never freed!

✔ Solution: Set variables to null when no longer needed.


function outer() {
let bigData = new Array(1000000).fill("Memory Leak");
return function inner() {
console.log(bigData.length);
bigData = null; // Release memory
};
}
let leak = outer();

22.5 Best Practices for Memory Optimization


✅ 1. Avoid Unnecessary Global Variables
Use local variables instead of globals.

✅ 2. Use WeakMap for Temporary Objects


WeakMap does not prevent garbage collection.
let cache = new WeakMap();
function storeData(key, value) {
cache.set(key, value);
}
storeData({ name: "Dhanian" }, "Data");

Since the key is an object, it is automatically garbage collected when no longer used.

✅ 3. Clear Unused Variables


let data = new Array(1000000).fill("Data");
data = null; // Frees up memory

✅ 4. Remove Event Listeners


Always detach listeners when elements are removed.
element.removeEventListener("click", handler);

✅ 5. Use RequestAnimationFrame Instead of setInterval


setInterval() can lead to excessive memory usage. Use requestAnimationFrame() for
smooth UI updates.
function animate() {
console.log("Animating...");
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

22.6 Summary & Next Steps


🎯 Key Takeaways:
✔ JavaScript manages memory automatically using Garbage Collection.
✔ Common memory leaks occur due to global variables, timers, event listeners, and closures.
✔ Best practices include using WeakMap, clearing timers, and removing event listeners.

Chapter 23: JavaScript Design Patterns &


Code Organization
Design patterns are proven solutions to common programming problems. They help in writing
scalable, maintainable, and efficient JavaScript code. In this chapter, we’ll explore:
• Common JavaScript design patterns
• When and how to use them
• Best practices for structuring JavaScript projects
• Real-world examples of design patterns in action
23.1 What Are Design Patterns?
A design pattern is a reusable solution to a common coding problem. It provides a structured
approach to solving issues efficiently.
✔ Why Use Design Patterns?
• Improves code readability
• Increases reusability and maintainability
• Reduces redundant code
• Helps in writing scalable applications

23.2 Categories of JavaScript Design Patterns


JavaScript design patterns fall into three main categories:

Category Description
Creational Patterns How objects are created efficiently
Structural Patterns How objects and classes are structured
Behavioral Patterns How objects interact with each other

23.3 Creational Design Patterns


Creational patterns deal with object creation in an optimized way.

1️⃣ Factory Pattern

📌
The Factory Pattern provides a way to create objects without specifying their exact class.
Example: Factory Pattern
class Car {
constructor(brand, model) {
this.brand = brand;
this.model = model;
}

drive() {
console.log(`Driving a ${this.brand} ${this.model}`);
}
}

class CarFactory {
static createCar(brand, model) {
return new Car(brand, model);
}
}

// Usage
const myCar = CarFactory.createCar("Toyota", "Corolla");
myCar.drive(); // Driving a Toyota Corolla
✔ When to Use?
• When the object creation logic is complex.
• When multiple object types are needed dynamically.

2️⃣ Singleton Pattern

📌
The Singleton Pattern ensures only one instance of a class exists.
Example: Singleton Pattern
class Database {
constructor() {
if (!Database.instance) {
this.connection = "Connected to Database";
Database.instance = this;
}
return Database.instance;
}

getConnection() {
return this.connection;
}
}

// Usage
const db1 = new Database();
const db2 = new Database();

console.log(db1 === db2); // true (both are the same instance)

✔ When to Use?
• To manage global application state.
• For database connections or caching.

23.4 Structural Design Patterns


Structural patterns focus on object composition and relationships.

3️⃣ Module Pattern

📌
The Module Pattern helps in encapsulating variables and functions, keeping them private.
Example: Module Pattern
const UserModule = (function () {
let users = [];

return {
addUser(name) {
users.push(name);
console.log(`${name} added!`);
},
listUsers() {
return users;
},
};
})();

// Usage
UserModule.addUser("Dhanian");
console.log(UserModule.listUsers()); // ["Dhanian"]

✔ When to Use?
• To create private variables and methods.
• To avoid global scope pollution.

4️⃣ Observer Pattern

📌
The Observer Pattern allows multiple objects to react to changes in a subject.
Example: Observer Pattern
class EventObserver {
constructor() {
this.subscribers = [];
}

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

unsubscribe(listener) {
this.subscribers = this.subscribers.filter(sub => sub !== listener);
}

notify(data) {
this.subscribers.forEach(listener => listener(data));
}
}

// Usage
const observer = new EventObserver();

const logEvent = (data) => console.log("Event received:", data);


observer.subscribe(logEvent);

observer.notify("User logged in"); // Event received: User logged in

✔ When to Use?
• When multiple parts of an app need to react to changes (e.g., event handling).

23.5 Behavioral Design Patterns


Behavioral patterns focus on communication between objects.
5️⃣ Strategy Pattern
The Strategy Pattern defines a family of algorithms and lets the user switch between them

📌
dynamically.
Example: Strategy Pattern
class PaymentStrategy {
pay(amount) {
throw new Error("Method not implemented");
}
}

class CreditCardPayment extends PaymentStrategy {


pay(amount) {
console.log(`Paid $${amount} using Credit Card.`);
}
}

class PayPalPayment extends PaymentStrategy {


pay(amount) {
console.log(`Paid $${amount} using PayPal.`);
}
}

// Context
class PaymentProcessor {
setStrategy(strategy) {
this.strategy = strategy;
}

executePayment(amount) {
this.strategy.pay(amount);
}
}

// Usage
const paymentProcessor = new PaymentProcessor();
paymentProcessor.setStrategy(new CreditCardPayment());
paymentProcessor.executePayment(100);

paymentProcessor.setStrategy(new PayPalPayment());
paymentProcessor.executePayment(50);

✔ When to Use?
• When multiple algorithms need to be interchangeable (e.g., payment methods).

23.6 Organizing JavaScript Projects for Scalability


Writing clean, modular JavaScript is crucial for building large applications.
✔ Best Practices for Code Organization
1. Follow the Single Responsibility Principle (SRP) – Each function should do one thing.
2. Use Modules – Keep functions and classes inside separate modules/files.
3. Use Meaningful Names – Use descriptive variable & function names.
4. Avoid Deep Nesting – Too many nested loops or conditions make code hard to read.
5. Follow DRY Principle – Don’t Repeat Yourself – Reuse code instead of duplicating.

23.7 Summary & Next Steps


🎯 Key Takeaways
✔ Design patterns provide structured solutions to common coding problems.
✔ Creational patterns help in object creation (Factory, Singleton).
✔ Structural patterns organize code and reduce complexity (Module, Observer).
✔ Behavioral patterns help in object interaction (Strategy).
✔ Following best practices leads to scalable and maintainable JavaScript projects.

Chapter 24: JavaScript Performance


Optimization Techniques
Performance optimization is crucial for building fast, responsive applications. In this chapter, we’ll
cover:
• How JavaScript engines work and optimize code
• Efficient coding techniques to improve performance
• Best practices for memory management
• Performance debugging tools

24.1 How JavaScript Engines Optimize Code


Before diving into optimizations, let’s understand how JavaScript engines interpret and execute
JavaScript code.

📌 JavaScript Execution Pipeline


1. Parsing: The JavaScript engine converts the code into an Abstract Syntax Tree (AST).
2. Compilation: Instead of interpreting JavaScript line-by-line, modern engines compile it to
machine code using Just-In-Time (JIT) compilation.
3. Optimization: JavaScript engines optimize the code dynamically using hidden classes,
inline caching, and dead code elimination.
✔ Popular JavaScript Engines:
• V8 (Google Chrome, Node.js)
• SpiderMonkey (Mozilla Firefox)
• JavaScriptCore (Safari)
📌 How to Write Code That JavaScript Engines Optimize Well?
• Use consistent object structures
• Avoid unnecessary type conversions
• Minimize the use of global variables

24.2 Memory Management & Garbage Collection


JavaScript automatically manages memory using garbage collection. However, poor memory
management can lead to performance issues and memory leaks.

🚀 Best Practices for Memory Optimization


✔ Avoid Memory Leaks:
• Remove event listeners when they are no longer needed.

📌
• Use weak references for large data structures.
Example: Preventing Memory Leaks
function setupEvent() {
let element = document.getElementById("btn");
element.addEventListener("click", function () {
console.log("Button Clicked");
});

// Remove event listener when done


element.removeEventListener("click", arguments.callee);
}

✔ Use Efficient Data Structures:


• Use maps and sets instead of objects for large collections.
• Use typed arrays for handling binary data.

24.3 Optimizing Loops and Iterations


Loops can significantly impact performance, so optimizing them is essential.

🚀 Best Practices for Loop Optimization


✔ Use for loop instead of forEach for performance-critical tasks
let arr = new Array(1000000).fill(0);

// Faster
for (let i = 0; i < arr.length; i++) {
arr[i] = i * 2;
}

// Slower
arr.forEach((value, index) => {
arr[index] = index * 2;
});
✔ Use map(), reduce(), filter() for array operations instead of traditional loops

✔ Cache array.length in a variable instead of computing it in each iteration


// Slower
for (let i = 0; i < arr.length; i++) {}

// Faster
for (let i = 0, len = arr.length; i < len; i++) {}

24.4 Asynchronous Optimization


Blocking operations (e.g., network requests, large computations) freeze the browser. The
solution? Asynchronous JavaScript!

🚀 Using Async/Await & Promises for Performance


✔ Use async/await instead of callback hell

📌
✔ Use Promise.all() to run multiple promises in parallel

Example: Parallel API Requests for Better Performance


async function fetchData() {
let [users, posts] = await Promise.all([
fetch("/api/users").then(res => res.json()),
fetch("/api/posts").then(res => res.json())
]);

console.log(users, posts);
}

24.5 DOM Optimization Techniques


The Document Object Model (DOM) is expensive to manipulate. Poor DOM handling slows
down JavaScript performance.

🚀 Best Practices for Optimizing DOM Manipulation


✔ Use documentFragment to minimize reflows and repaints
let fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
let div = document.createElement("div");
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
document.body.appendChild(fragment);

✔ Minimize reflows and repaints


• Batch DOM updates instead of modifying the DOM in loops.
• Use CSS classes instead of changing individual styles dynamically.
✔ Use Virtual DOM for large-scale updates
• React, Vue use a Virtual DOM to minimize performance overhead.

24.6 Lazy Loading & Code Splitting


Loading unnecessary JavaScript upfront slows down web apps.

🚀 How to Optimize Loading Performance?


✔ Lazy Load Images & Content
<img src="placeholder.jpg" data-src="real-image.jpg" class="lazy-load">

✔ Code Splitting with Dynamic Imports


import("./module.js").then(module => {
module.run();
});

✔ Minify & Compress JavaScript Files


• Use UglifyJS, Terser for minification.
• Enable Gzip or Brotli compression in the server.

24.7 Profiling & Debugging Performance


Chrome DevTools and Lighthouse help analyze performance bottlenecks.

🚀 How to Analyze JavaScript Performance?


✔ Use Chrome Performance Tab to detect slow scripts
✔ Measure memory leaks with Chrome’s Memory Profiler

📌
✔ Analyze network performance using Chrome Network Tab
Example: Using console.time() to Benchmark Performance
console.time("loop");
for (let i = 0; i < 1000000; i++) {}
console.timeEnd("loop"); // Logs execution time

24.8 Summary & Next Steps


🎯 Key Takeaways
✔ Optimize loops and avoid expensive operations
✔ Use async/await to handle asynchronous operations efficiently
✔ Minimize unnecessary DOM manipulations
✔ Lazy load assets and split code for faster page loads
✔ Use Chrome DevTools to identify and fix performance issues

Chapter 25: JavaScript Security Best Practices


Security is a critical aspect of JavaScript development. Without proper security measures, web
applications become vulnerable to hacking, data theft, and unauthorized access. In this chapter,
we’ll cover:
• Common JavaScript security vulnerabilities
• How to prevent attacks like XSS, CSRF, and Clickjacking
• Best practices for writing secure JavaScript code

25.1 Understanding Common JavaScript Security


Vulnerabilities
Before we dive into security solutions, let’s understand the most common security risks in
JavaScript applications.

🚨 1. Cross-Site Scripting (XSS)


What is it?

📌
XSS allows attackers to inject malicious scripts into a website, affecting users who visit it.
Example: A Vulnerable Search Bar
app.get("/search", (req, res) => {
let query = req.query.q;
res.send(`<h1>Results for: ${query}</h1>`);
});

🚨 If a user enters <script>alert('Hacked!')</script>, the site executes it!

✅ How to Prevent XSS?


✔ Sanitize user input using libraries like DOMPurify
✔ Escape HTML characters before displaying user input

📌
✔ Use Content Security Policy (CSP)
Example: Secure User Input Handling
const sanitizeHTML = require("sanitize-html");
app.get("/search", (req, res) => {
let query = sanitizeHTML(req.query.q);
res.send(`<h1>Results for: ${query}</h1>`);
});
🚨 2. Cross-Site Request Forgery (CSRF)
What is it?
CSRF tricks a logged-in user into executing unwanted actions (e.g., transferring money, changing

📌
passwords).
Example: CSRF Attack in a Banking App
A malicious website tricks a logged-in user into visiting:
<img src="https://bank.com/transfer?amount=1000&to=hacker">

🚨 Since the user is already logged in, the browser sends the request without user consent!

✅ How to Prevent CSRF?


✔ Use CSRF tokens to verify legitimate requests

📌
✔ Use SameSite cookies to prevent unauthorized cross-origin requests
Example: Generating a CSRF Token
const csrf = require("csurf");
app.use(csrf());
app.get("/form", (req, res) => {
res.send(`<input type="hidden" name="_csrf" value="${req.csrfToken()}">`);
});

🚨 3. Clickjacking
What is it?
Attackers embed your website in an invisible iframe and trick users into clicking buttons

📌
unknowingly.
Example: A Malicious Website Loads Your App in an iframe
<iframe src="https://yourbank.com/transfer" style="opacity:0;
position:absolute;"></iframe>

🚨 The victim thinks they are clicking on something else but actually submits a transfer request!

✅ How to Prevent Clickjacking?


✔ Use X-Frame-Options Header to prevent embedding your site in iframes.

📌
✔ Use Content Security Policy (CSP) to restrict framing.
Example: Setting Secure Headers in Express.js
const helmet = require("helmet");
app.use(helmet.frameguard({ action: "deny" }));

25.2 Secure JavaScript Coding Best Practices


Even if you avoid common vulnerabilities, poor coding practices can expose your app to attacks.

🚨
1. Avoid Using eval() & setTimeout() with Strings

📌❌
Why? eval() and setTimeout(string) allow attackers to execute arbitrary code.

Bad Example: Using eval()

eval(userInput); // 🚨
let userInput = "alert('Hacked!')";
Executes malicious code

📌✅ Secure Alternative
let userInput = JSON.parse('{"message":"Hello"}');
console.log(userInput.message);

✅ 2. Use HTTPS and Secure Cookies


✔ Always serve JavaScript over HTTPS

📌
✔ Set secure cookie attributes (Secure, HttpOnly, SameSite)

Example: Setting Secure Cookies


res.cookie("session", "random_token", {
httpOnly: true,
secure: true,
sameSite: "Strict"
});

✅ 3. Restrict User Input Length and Type


✔ Validate all user input before processing it.

📌
✔ Restrict file uploads to prevent malicious file execution.
Example: Validating User Input
function validateInput(input) {
if (input.length > 100 || /[^a-zA-Z0-9]/.test(input)) {
throw new Error("Invalid input");
}
}

✅ 4. Keep Dependencies Updated & Use Security Scanners


✔ Regularly update NPM packages to patch security flaws.

📌
✔ Use tools like npm audit and Snyk to detect vulnerabilities.

Example: Checking for Security Issues


npm audit fix
25.3 Security Headers & Policies for JavaScript Applications
Adding security headers can protect your JavaScript apps from common attacks.

🚀 Essential Security Headers


Header Protection Against
X-Frame-Options: DENY Prevents Clickjacking
X-Content-Type-Options: nosniff Blocks MIME-type attacks

📌
Content-Security-Policy (CSP) Prevents XSS & Data Injection
Example: Setting Security Headers in Express.js
const helmet = require("helmet");
app.use(helmet());

25.4 Summary & Next Steps


🎯 Key Takeaways
✔ Sanitize user input to prevent XSS
✔ Use CSRF tokens to protect against Cross-Site Request Forgery
✔ Prevent Clickjacking with X-Frame-Options
✔ Avoid eval() and use secure coding practices
✔ Use HTTPS and set secure cookies
✔ Update dependencies & use security tools like npm audit

Chapter 26: JavaScript Best Practices for


Large-Scale Applications
Building a large-scale JavaScript application comes with its own set of challenges. As projects grow
in complexity, maintaining clean, efficient, and scalable code becomes crucial. In this chapter, we’ll
explore:
• Code structuring techniques for large applications
• Best practices for maintainability and performance
• How to handle dependencies and optimize builds

26.1 Structuring Large JavaScript Applications


As an application scales, a well-defined project structure helps maintain readability and
scalability.
🚀 The Modular Approach

📌
Instead of writing all JavaScript in one file, break it into reusable modules.
Example: Organizing a Large JavaScript Project
/my-app
│── /src
│ ├── /components # Reusable UI Components
│ ├── /services # API Calls & Business Logic
│ ├── /utils # Helper Functions
│ ├── /store # State Management (if using Redux/Vuex)
│ ├── index.js # Main Entry File
│── package.json
│── webpack.config.js
│── .gitignore

📌 Example: Using ES Modules for Clean Code


// utils/math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}

// main.js
import { add, subtract } from "./utils/math.js";
console.log(add(10, 5)); // 15

✅ Benefits:
✔ Improves maintainability
✔ Makes code reusable
✔ Easier debugging

26.2 Writing Maintainable JavaScript Code


To ensure your code remains readable and manageable over time, follow these best practices:

📌❌
1. Use Meaningful Variable and Function Names
Bad Example
let x = 10;
let y = 20;
function calc(a, b) {
return a * b;
}

📌✅ Good Example
let productPrice = 10;
let discount = 20;
function calculateTotal(price, discount) {
return price - discount;
}
🚨
2. Follow the DRY (Don't Repeat Yourself) Principle

📌❌
Repeating the same logic throughout your code makes it hard to maintain.
Bad Example (Repeated Code)
let user = { name: "Dhanian", age: 25 };
console.log(`User: ${user.name}, Age: ${user.age}`);

let admin = { name: "Admin", age: 30 };


console.log(`Admin: ${admin.name}, Age: ${admin.age}`);

📌✅ Good Example (Reusable Function)


function printUserInfo(user) {
console.log(`User: ${user.name}, Age: ${user.age}`);
}
printUserInfo({ name: "Dhanian", age: 25 });
printUserInfo({ name: "Admin", age: 30 });

26.3 Performance Optimization in Large Applications


🚀 1. Minimize Global Variables

📌❌
Global variables can cause conflicts in large applications.
Bad Example (Global Variable Pollution)
var user = "Dhanian";
function getUser() {
console.log(user);
}

📌✅ Good Example (Encapsulated Variables)


(function () {
let user = "Dhanian";
function getUser() {
console.log(user);
}
})();

🚀 2. Use Lazy Loading to Improve Page Speed

📌
Instead of loading all JavaScript files at once, load them only when needed.
Example: Lazy Loading a Module
document.getElementById("loadMore").addEventListener("click", async () => {
let module = await import("./extraFunctions.js");
module.sayHello();
});

🚀
📌❌
3. Optimize Loops for Performance
Bad Example (Inefficient Loop)
let numbers = [1, 2, 3, 4, 5];
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}

📌✅ Good Example (Using forEach or map)


numbers.forEach(num => console.log(num));

26.4 Handling Dependencies in Large Applications


Using too many dependencies can slow down performance and create security risks.

📌
1. Keep Dependencies Minimal
Check for unnecessary packages
npm list --depth=0

📌 Remove unused dependencies


npm prune

2. Lock Dependency Versions

📌
Always lock versions to avoid breaking changes.
Use package-lock.json or yarn.lock
npm ci

26.5 Debugging and Error Handling Best Practices


Large applications require efficient debugging strategies.

📌
1. Use try...catch for Error Handling
Example: Handling API Errors Gracefully
async function fetchData() {
try {
let response = await fetch("https://api.example.com/data");
if (!response.ok) throw new Error("Failed to fetch data");
let data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error.message);
}
}
fetchData();

🚀
2. Use Debugging Tools like Chrome DevTools
Breakpoints & Console Debugging help in finding errors faster.
console.log("Debugging message");
debugger; // Stops execution in DevTools

26.6 Version Control & Code Collaboration


Using Git effectively helps teams manage changes in large applications.

🚀 1. Follow a Git Branching Strategy

📌
Use the Git Flow Model:
Example Branches
• main → Stable production-ready code
• develop → Active development
• feature/new-feature → New feature in progress
git checkout -b feature/authentication

🚀
📌❌
2. Write Clear Commit Messages
Bad Commit Message
git commit -m "fixed bug"

📌✅ Good Commit Message


git commit -m "Fix: Resolved issue with user authentication"

26.7 Summary & Next Steps


🎯 Key Takeaways
✔ Modularize code for maintainability
✔ Use meaningful variable names and follow DRY principles
✔ Optimize performance by reducing global variables and using lazy loading
✔ Keep dependencies minimal and locked to specific versions
✔ Handle errors gracefully with try...catch and use debugging tools
✔ Follow a Git branching strategy for collaboration

Chapter 27: JavaScript Design Patterns for


Scalable Applications
As JavaScript applications grow in complexity, maintaining clean, efficient, and scalable code
becomes essential. Design patterns provide reusable solutions to common problems, making
development more structured and maintainable.
In this chapter, we’ll cover:
• Why design patterns matter in JavaScript
• Common JavaScript design patterns (with real-world examples)
• Best practices for using design patterns in large applications

27.1 What Are Design Patterns?


A design pattern is a general, reusable solution to a commonly occurring problem in software


design. It’s not a code snippet but rather a guideline for structuring your application.
Why Use Design Patterns?
✔ Improve code reusability
✔ Make code easier to maintain and scale
✔ Reduce common bugs and pitfalls
✔ Enhance collaboration in teams

27.2 Creational Design Patterns


Creational patterns focus on object creation strategies.

1. The Singleton Pattern

📌
Ensures that a class has only one instance and provides a global access point to that instance.

📌
Use Case: Managing application state (e.g., global configuration, database connection).
Example: Singleton in JavaScript
class Database {
constructor() {
if (!Database.instance) {
this.connection = "Database Connected";
Database.instance = this;
}
return Database.instance;
}
}

const db1 = new Database();


const db2 = new Database();

console.log(db1 === db2); // true (same instance)

✅ When to Use:
✔ Managing a global state
✔ Caching expensive operations
2. Factory Pattern

📌
A Factory Pattern creates objects without exposing the instantiation logic.

📌
Use Case: When we need to create different types of objects dynamically.
Example: Factory Pattern for Creating Users
class User {
constructor(name, role) {
this.name = name;
this.role = role;
}
}

class UserFactory {
createUser(name, role) {
return new User(name, role);
}
}

const factory = new UserFactory();


const admin = factory.createUser("Dhanian", "Admin");
const guest = factory.createUser("Alice", "Guest");

console.log(admin, guest);

✅ When to Use:
✔ When object creation logic needs to be centralized
✔ When creating multiple similar objects dynamically

27.3 Structural Design Patterns


Structural patterns help organize relationships between objects and classes.

1. The Module Pattern

📌
Encapsulates variables and methods within a single scope using closures.

📌
Use Case: Helps avoid polluting the global scope.
Example: Using Module Pattern
const UserModule = (function () {
let users = []; // Private variable

function addUser(user) {
users.push(user);
console.log(`${user} added!`);
}

function getUsers() {
return users;
}

return {
addUser,
getUsers,
};
})();

UserModule.addUser("Dhanian");
console.log(UserModule.getUsers()); // ['Dhanian']

✅ When to Use:
✔ To encapsulate logic and avoid global scope pollution
✔ To protect private variables

2. The Decorator Pattern

📌
Allows dynamically adding behaviors to an object without modifying its structure.

📌
Use Case: When adding new functionality to an object without modifying its existing code.
Example: Adding Features to a Car Object
class Car {
constructor() {
this.description = "Basic Car";
}

getDescription() {
return this.description;
}

cost() {
return 10000;
}
}

// Decorator
class Sunroof {
constructor(car) {
this.car = car;
}

getDescription() {
return `${this.car.getDescription()} with Sunroof`;
}

cost() {
return this.car.cost() + 1500;
}
}

let myCar = new Car();


myCar = new Sunroof(myCar);

console.log(myCar.getDescription()); // "Basic Car with Sunroof"


console.log(myCar.cost()); // 11500

✅ When to Use:
✔ When adding new features to an object dynamically
✔ When extending functionality without modifying the base object
27.4 Behavioral Design Patterns
Behavioral patterns focus on communication between objects.

1. The Observer Pattern

📌
Defines a one-to-many dependency where changes in one object trigger updates in others.

📌
Use Case: Real-time notifications, event handling.
Example: Observer Pattern for a News Feed
class NewsFeed {
constructor() {
this.subscribers = [];
}

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

notify(news) {
this.subscribers.forEach(subscriber => subscriber.update(news));
}
}

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

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

const newsFeed = new NewsFeed();


const user1 = new User("Dhanian");
const user2 = new User("Alice");

newsFeed.subscribe(user1);
newsFeed.subscribe(user2);

newsFeed.notify("New JavaScript update released!");

✅ When to Use:
✔ When multiple objects need to react to state changes
✔ When implementing event-driven architectures

2. The Strategy Pattern


Defines a family of algorithms, encapsulates them, and allows switching between them

📌
dynamically.

📌
Use Case: When we need multiple ways to execute a function (e.g., different sorting methods).
Example: Strategy Pattern for Sorting Algorithms
class BubbleSort {
sort(array) {
console.log("Sorting with Bubble Sort");
return array.sort((a, b) => a - b);
}
}

class QuickSort {
sort(array) {
console.log("Sorting with Quick Sort");
return array.sort((a, b) => a - b);
}
}

class SortContext {
constructor(strategy) {
this.strategy = strategy;
}

executeStrategy(array) {
return this.strategy.sort(array);
}
}

// Using different strategies


let numbers = [5, 2, 8, 1, 3];

let sorter = new SortContext(new BubbleSort());


console.log(sorter.executeStrategy(numbers));

sorter = new SortContext(new QuickSort());


console.log(sorter.executeStrategy(numbers));

✅ When to Use:
✔ When multiple strategies (algorithms) can be used interchangeably
✔ When avoiding large if-else conditions

27.5 Summary & Next Steps


🎯 Key Takeaways
✔ Singleton Pattern - Ensures a single instance
✔ Factory Pattern - Creates objects dynamically
✔ Module Pattern - Encapsulates logic, prevents global scope pollution
✔ Decorator Pattern - Dynamically adds behaviors to objects
✔ Observer Pattern - Implements event-driven programming
✔ Strategy Pattern - Allows interchangeable algorithms

Chapter 28: JavaScript Security Best Practices


Security is a crucial aspect of JavaScript development, especially when working with web
applications. Poor security practices can lead to vulnerabilities such as cross-site scripting (XSS),
SQL injection, data breaches, and session hijacking.
In this chapter, we’ll cover:
• Common security threats in JavaScript applications
• Best practices for securing frontend and backend JavaScript
• How to implement secure authentication and authorization

28.1 Understanding Common Security Threats


JavaScript applications, particularly those running in browsers and on servers (Node.js), are
vulnerable to various security risks. Below are some of the most common threats:

1. Cross-Site Scripting (XSS)


XSS occurs when an attacker injects malicious JavaScript into a web page, which then runs in the
user's browser. This can be used to steal session cookies, deface a website, or redirect users to

📌
malicious sites.
Example of XSS Attack:
<input type="text" id="commentBox" />
<button onclick="postComment()">Submit</button>

<script>
function postComment() {
let userInput = document.getElementById("commentBox").value;
document.body.innerHTML += `<p>${userInput}</p>`; // Vulnerable to XSS!
}
</script>

✅ How to Prevent XSS:


✔ Escape user input before rendering it
✔ Use Content Security Policy (CSP) to block inline scripts

📌
✔ Use libraries like DOMPurify to sanitize user input
Secure Example (Sanitizing Input):
import DOMPurify from "dompurify";

function postComment() {
let userInput = document.getElementById("commentBox").value;
let sanitizedInput = DOMPurify.sanitize(userInput);
document.body.innerHTML += `<p>${sanitizedInput}</p>`;
}

2. SQL Injection (For Node.js with Databases)


SQL Injection occurs when an attacker manipulates a database query by injecting malicious SQL

📌
code.
Example of Vulnerable Code in Node.js (Express + MySQL):
app.get("/user", (req, res) => {
let userId = req.query.id;
db.query(`SELECT * FROM users WHERE id = '${userId}'`, (err, result) => {
if (err) throw err;
res.json(result);
});
});

👉
👉
If an attacker enters: 1' OR '1'='1
The query becomes:
SELECT * FROM users WHERE id = '1' OR '1'='1';

✅ How to Prevent SQL Injection:


✔ Use parameterized queries

📌
✔ Use an ORM like Sequelize or Mongoose
Secure Example (Using Parameterized Queries):
app.get("/user", (req, res) => {
let userId = req.query.id;
db.query("SELECT * FROM users WHERE id = ?", [userId], (err, result) => {
if (err) throw err;
res.json(result);
});
});

3. Cross-Site Request Forgery (CSRF)

📌
CSRF tricks a logged-in user into performing unintended actions on a website without their consent.
Example Attack Scenario:
1. A user logs into a banking website.
2. The attacker sends the user a link to a malicious site that contains:
<img src="https://bank.com/transfer?amount=10000&to=attacker123" />


3. If the user is still logged in, the request is executed, transferring funds to the attacker.
How to Prevent CSRF:
✔ Implement CSRF tokens in all sensitive forms

📌
✔ Use the SameSite cookie attribute
Example of Secure CSRF Protection in Express.js:
import csurf from "csurf";
app.use(csurf());

app.get("/form", (req, res) => {


res.send(`<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="${req.csrfToken()}">
<button type="submit">Transfer</button>
</form>`);
});
4. Insecure Authentication & JWT Exploits


Improper authentication can lead to account takeovers, token hijacking, and brute-force attacks.
Best Practices for Secure Authentication:
✔ Use strong password hashing (bcrypt)
✔ Implement JWT expiration and blacklisting

📌
✔ Enforce multi-factor authentication (MFA)
Secure Example (Password Hashing with bcrypt.js in Node.js):
import bcrypt from "bcrypt";

const saltRounds = 10;


const hashedPassword = await bcrypt.hash("my_secure_password", saltRounds);
console.log(hashedPassword);

📌 Secure JWT Implementation in Node.js:


import jwt from "jsonwebtoken";

// Generate token
const token = jwt.sign({ userId: 123 }, "secretKey", { expiresIn: "1h" });

// Verify token
jwt.verify(token, "secretKey", (err, decoded) => {
if (err) {
console.log("Invalid Token");
} else {
console.log("Valid Token:", decoded);
}
});

28.2 Secure Frontend JavaScript Practices


✅ 1. Avoid Using eval()


The eval() function executes arbitrary JavaScript code, making it highly dangerous.

Bad Code (Unsafe Execution with eval)


let userInput = "alert('Hacked!')";
eval(userInput); // Executes malicious code

✅ Secure Alternative:
const safeFunction = new Function("return 42;");
console.log(safeFunction()); // 42

✅ 2. Secure Cookies & Local Storage


✔ Avoid storing sensitive data in localStorage (vulnerable to XSS).
✔ Use HttpOnly & Secure cookies.
📌 Secure Example (Setting Secure Cookies in Express.js):
app.use(cookieParser());

app.get("/set-cookie", (req, res) => {


res.cookie("sessionID", "abc123", {
httpOnly: true,
secure: true,
sameSite: "Strict",
});
res.send("Secure cookie set!");
});


📌
3. Use helmet for HTTP Security Headers
Example of Secure Headers with Helmet.js in Express:
import helmet from "helmet";
app.use(helmet());

Helmet sets headers like:


✔ X-Frame-Options (Prevents Clickjacking)
✔ X-XSS-Protection (Prevents XSS)
✔ Content-Security-Policy (Restricts script execution)

28.3 Summary & Next Steps


🎯 Key Takeaways
✔ Escape and sanitize user input to prevent XSS
✔ Use parameterized queries to prevent SQL Injection
✔ Implement CSRF tokens for protection against CSRF attacks
✔ Use bcrypt.js for password hashing
✔ Secure JWT authentication and handle token expiration
✔ Set HttpOnly & Secure cookies to prevent session hijacking
✔ Use helmet.js to enforce HTTP security headers

Chapter 29: JavaScript Performance


Optimization
Performance is a crucial aspect of JavaScript development, affecting user experience, load times,
and scalability. Optimizing JavaScript code ensures faster execution, reduced memory usage, and
better responsiveness in both frontend and backend applications.


In this chapter, we’ll cover:

✅ How JavaScript works under the hood

✅ Best practices for optimizing JavaScript code


Efficient data structures and algorithms

✅ Browser rendering performance optimization
Node.js performance optimization techniques

29.1 How JavaScript Works Under the Hood


Before diving into optimization techniques, let's understand how JavaScript executes code.

1. JavaScript Execution in the Browser


When a browser loads a webpage, it follows these steps:
1. Parse HTML → Build the DOM (Document Object Model)
2. Parse CSS → Build the CSSOM (CSS Object Model)
3. Parse & Execute JavaScript → Runs scripts & manipulates the DOM
4. Repaint & Reflow → Renders content on the screen
JavaScript runs in a single-threaded event loop, meaning it processes one operation at a time.
Blocking the main thread (e.g., using heavy loops or synchronous functions) can cause the UI to
freeze.

2. JavaScript Execution in Node.js


Node.js runs JavaScript on the V8 Engine and handles asynchronous operations using:
• The Event Loop (Non-blocking execution)
• Worker Threads (For parallel processing)
• Asynchronous APIs (e.g., fs.readFile, fetch())

29.2 Best Practices for Optimizing JavaScript Code


✅ 1. Minimize DOM Manipulation
Updating the DOM is expensive. Instead of modifying the DOM multiple times, batch updates


together.
Bad Code (Frequent DOM Updates - Slow Performance)
const list = document.getElementById("list");
for (let i = 0; i < 1000; i++) {
const item = document.createElement("li");
item.textContent = `Item ${i}`;
list.appendChild(item); // Multiple reflows (slow)
}

✅ Optimized Code (Using Document Fragment)


const list = document.getElementById("list");
const fragment = document.createDocumentFragment();

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


const item = document.createElement("li");
item.textContent = `Item ${i}`;
fragment.appendChild(item);
}

list.appendChild(fragment); // Single reflow (fast)

✅ 2. Use Efficient Loops & Avoid forEach() in Performance-Critical Code


Loops can slow down performance, especially when dealing with large data sets.
Bad Code (Slow forEach() Loop on Large Arrays)
const numbers = Array(1e6).fill(0);
numbers.forEach((num, index) => {
numbers[index] = num + 1;
});

✅ Optimized Code (for Loop is Faster)


const numbers = Array(1e6).fill(0);
for (let i = 0; i < numbers.length; i++) {
numbers[i] += 1;
}

📌 Why?
✔ forEach() is slower because it uses a callback function, which introduces extra function
calls.
✔ for loops run faster as they don’t have function overhead.

✅ 3. Optimize Event Listeners


Excessive event listeners slow down performance and increase memory usage.
Bad Code (Multiple Event Listeners on Similar Elements)
document.getElementById("btn1").addEventListener("click", handleClick);
document.getElementById("btn2").addEventListener("click", handleClick);
document.getElementById("btn3").addEventListener("click", handleClick);

✅ Optimized Code (Event Delegation)


document.getElementById("buttonsContainer").addEventListener("click", (event) =>
{
if (event.target.tagName === "BUTTON") {
handleClick(event);
}
});

✔ Why is this faster?


Instead of adding multiple event listeners, a single listener is added to a parent element and
captures clicks on all buttons.
✅ 4. Avoid Memory Leaks
Memory leaks occur when objects are not properly released, causing unnecessary memory


consumption.
Bad Code (Creating Detached DOM Elements)
let element = document.getElementById("myElement");
element.remove();
console.log(element.innerHTML); // Still accessible (memory leak)

✅ Optimized Code (Manually Nullify References to Free Memory)


let element = document.getElementById("myElement");
element.remove();
element = null; // Frees memory

✅ 5. Debounce & Throttle Expensive Operations

📌
Functions that run too frequently (e.g., scroll, resize) reduce performance.
Debounce (Limits Function Execution After a Delay)
function debounce(func, delay) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}

window.addEventListener("resize", debounce(() => console.log("Resized!"), 300));

📌 Throttle (Limits Function Execution to Every X Milliseconds)


function throttle(func, limit) {
let lastFunc, lastTime;
return function (...args) {
const now = Date.now();
if (!lastTime || now - lastTime >= limit) {
func.apply(this, args);
lastTime = now;
}
};
}

window.addEventListener("scroll", throttle(() => console.log("Scrolling..."),


500));

✔ Why is this useful?


• Debounce is useful for search inputs (waits before executing).
• Throttle is useful for scrolling & resizing events (limits execution).
29.3 Optimizing Node.js Performance
✅ 1. Use Asynchronous Code for Non-Blocking Execution


Blocking code can slow down Node.js and cause performance bottlenecks.
Bad Code (Blocking Execution - Slows Down Requests)
app.get("/data", (req, res) => {
const data = fs.readFileSync("largeFile.txt", "utf8");
res.send(data);
});

✅ Optimized Code (Using Async fs.readFile)


app.get("/data", async (req, res) => {
const data = await fs.promises.readFile("largeFile.txt", "utf8");
res.send(data);
});

✅ 2. Use Clustering for High Traffic Applications

📌
Node.js runs on a single thread, but clustering allows us to utilize multiple CPU cores.
Enable Clustering in Node.js:
import cluster from "cluster";
import os from "os";

if (cluster.isPrimary) {
for (let i = 0; i < os.cpus().length; i++) {
cluster.fork();
}
} else {
// Start Express server
app.listen(3000, () => console.log("Server running"));
}

✔ Why?
• Clustering creates multiple processes, improving scalability.
• Each process runs independently, handling separate requests.

29.4 Summary & Next Steps


🎯 Key Takeaways
✔ Minimize DOM updates using document fragments
✔ Use for loops instead of forEach() for large arrays
✔ Optimize event handling using event delegation
✔ Use debouncing & throttling for scrolling & resizing
✔ Avoid memory leaks by properly removing references
✔ Optimize Node.js performance using asynchronous code & clustering
Chapter 30: JavaScript Design Patterns
Design patterns are reusable solutions to common coding problems. They help make code more
structured, maintainable, and scalable. Understanding design patterns is essential for writing
clean, efficient, and flexible JavaScript applications.

30.1 Why Use Design Patterns?



✅ Improve code readability and maintainability



Encourage reusable and modular code
Improve performance and scalability

✅ Follow best practices in software design


Avoid anti-patterns (bad coding practices)

30.2 Categories of JavaScript Design Patterns


JavaScript design patterns are broadly categorized into three types:
1. Creational Patterns → Object creation mechanisms
2. Structural Patterns → Organizing relationships between objects
3. Behavioral Patterns → Communication between objects

30.3 Creational Design Patterns (Object Creation)


1. Singleton Pattern

📌
Ensures only one instance of an object exists.
Example: Singleton in JavaScript
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
}

const instance1 = new Singleton();


const instance2 = new Singleton();

console.log(instance1 === instance2); // true (same instance)

✔ Why?
• Useful for global configuration settings (e.g., database connection, API clients).

2. Factory Pattern

📌
Encapsulates object creation logic without specifying the exact class.
Example: Factory Pattern in JavaScript
class Car {
constructor(model, engine) {
this.model = model;
this.engine = engine;
}
}

class CarFactory {
static createCar(type) {
if (type === "sedan") return new Car("Sedan", "V4");
if (type === "suv") return new Car("SUV", "V6");
}
}

const sedan = CarFactory.createCar("sedan");


console.log(sedan); // { model: 'Sedan', engine: 'V4' }

✔ Why?
• Useful when we need to create different objects dynamically.

30.4 Structural Design Patterns (Object Relationships)


3. Module Pattern

📌
Encapsulates private variables and exposes public methods.
Example: JavaScript Module Pattern
const Counter = (function () {
let count = 0;

return {
increment: function () {
count++;
console.log(count);
},
decrement: function () {
count--;
console.log(count);
},
getCount: function () {
return count;
},
};
})();

Counter.increment(); // 1
Counter.increment(); // 2
console.log(Counter.getCount()); // 2

✔ Why?
• Avoids global variable pollution.
• Useful for creating reusable modules in JavaScript.

4. Decorator Pattern

📌
Adds new functionalities to an object without modifying its structure.
Example: Decorator Pattern in JavaScript
function Coffee() {
this.cost = () => 5;
}

function MilkDecorator(coffee) {
const cost = coffee.cost();
coffee.cost = () => cost + 2;
}

const myCoffee = new Coffee();


MilkDecorator(myCoffee);
console.log(myCoffee.cost()); // 7

✔ Why?
• Used for adding extra features dynamically (e.g., plugins in software).

30.5 Behavioral Design Patterns (Communication Between


Objects)
5. Observer Pattern

📌
One object notifies multiple dependent objects when state changes (like event listeners).
Example: Observer Pattern in JavaScript
class Subject {
constructor() {
this.observers = [];
}

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

unsubscribe(observer) {
this.observers = this.observers.filter((obs) => obs !== observer);
}

notify(data) {
this.observers.forEach((observer) => observer.update(data));
}
}
class Observer {
update(data) {
console.log(`Received update: ${data}`);
}
}

const subject = new Subject();


const observer1 = new Observer();
const observer2 = new Observer();

subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify("New Data Available!");
// Output:
// Received update: New Data Available!
// Received update: New Data Available!

✔ Why?
• Used in event-driven architectures (like React’s state management).

6. Strategy Pattern

📌
Encapsulates multiple algorithms and selects the best one dynamically.
Example: Strategy Pattern in JavaScript
class PaymentStrategy {
pay(amount) {
throw new Error("Method not implemented.");
}
}

class PayPal extends PaymentStrategy {


pay(amount) {
console.log(`Paid ${amount} via PayPal`);
}
}

class CreditCard extends PaymentStrategy {


pay(amount) {
console.log(`Paid ${amount} via Credit Card`);
}
}

class PaymentProcessor {
constructor(strategy) {
this.strategy = strategy;
}

setStrategy(strategy) {
this.strategy = strategy;
}

processPayment(amount) {
this.strategy.pay(amount);
}
}

const payment = new PaymentProcessor(new PayPal());


payment.processPayment(100); // Paid 100 via PayPal

payment.setStrategy(new CreditCard());
payment.processPayment(200); // Paid 200 via Credit Card

✔ Why?
• Useful for selecting different algorithms dynamically (e.g., different sorting algorithms).

30.6 Summary & Next Steps


🎯 Key Takeaways
✔ Singleton → Ensures only one instance exists.
✔ Factory → Creates objects without specifying the exact class.
✔ Module → Encapsulates variables using closures.
✔ Decorator → Adds functionality without modifying the original object.
✔ Observer → Allows multiple objects to react to changes.
✔ Strategy → Switches between multiple algorithms dynamically.

Chapter 31: Advanced JavaScript Concepts


In this chapter, we’ll dive into some advanced JavaScript concepts that power modern
applications. Understanding these topics will help you write efficient, scalable, and high-
performance JavaScript code.

31.1 Understanding the JavaScript Event Loop


JavaScript is single-threaded, meaning it can only execute one task at a time. However, it can
handle asynchronous operations using the event loop.

How the Event Loop Works


1. Call Stack → Executes synchronous JavaScript code.
2. Web APIs → Handles asynchronous tasks (e.g., setTimeout, fetch).
3. Callback Queue → Stores callbacks waiting to be executed.

📌
4. Event Loop → Moves tasks from the queue to the stack when it's empty.
Example: Understanding Event Loop
console.log("Start");

setTimeout(() => {
console.log("Inside setTimeout");
}, 0);

console.log("End");
// Output:
// Start
// End
// Inside setTimeout (executed after stack is empty)

✔ Why?
• The event loop ensures non-blocking execution, making JavaScript fast and efficient.

31.2 Callbacks: The Foundation of Asynchronous JavaScript

📌
A callback function is a function passed as an argument to another function and executed later.
Example: Callback Function
function fetchData(callback) {
console.log("Fetching data...");
setTimeout(() => {
callback("Data received");
}, 2000);
}

fetchData((data) => console.log(data));


// Output:
// Fetching data...
// (After 2 seconds) Data received

✔ Why?
• Used in event handling, network requests, and async programming.

The Callback Hell Problem

📌
When multiple callbacks are nested, the code becomes hard to read.
Example: Callback Hell
getUser(1, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0], (details) => {
console.log(details);
});
});
});

✔ Solution? → Use Promises and async/await.

31.3 Promises: Handling Async Code Elegantly


A Promise represents a value that will be available sometime in the future.

States of a Promise
1. Pending → Initial state, operation not completed.
2. Fulfilled → Operation completed successfully.
📌
3. Rejected → Operation failed.
Example: Creating a Promise
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received!");
}, 2000);
});
};

fetchData().then((data) => console.log(data));


// Output (After 2 seconds): Data received!

✔ Why?
• Improves code readability compared to callbacks.

Handling Errors with .catch()


fetchData()
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));

31.4 Async/Await: The Modern Approach to Async Code

📌
The async and await keywords make asynchronous code look synchronous.

Example: Async/Await
const fetchData = async () => {
return "Data received!";
};

const getData = async () => {


const data = await fetchData();
console.log(data);
};

getData();
// Output: Data received!

✔ Why?
• Simplifies handling async operations (compared to .then() and .catch()).

Handling Errors with try...catch


const fetchData = async () => {
throw new Error("Network Error");
};

const getData = async () => {


try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error("Error:", error.message);
}
};

getData();
// Output: Error: Network Error

31.5 Web Workers: Running JavaScript in the Background


JavaScript is single-threaded, but Web Workers allow running scripts in parallel threads,

📌
preventing the UI from freezing.
Example: Creating a Web Worker
1️⃣Create a worker.js file:
onmessage = function (e) {
let result = e.data * 2;
postMessage(result);
};

2️⃣Use the Web Worker in the main script:


const worker = new Worker("worker.js");

worker.onmessage = function (e) {


console.log("Result:", e.data);
};

worker.postMessage(5);
// Output: Result: 10

✔ Why?
• Useful for CPU-intensive tasks (e.g., image processing, large calculations).

31.6 JavaScript Memory Management and Performance


Optimization
1. Garbage Collection
JavaScript uses automatic garbage collection to free memory.


2. Memory Leaks and How to Avoid Them


Global variables → Use let and const instead of var.


Unused event listeners → Remove them using .removeEventListener().

📌
Unreferenced objects in closures → Be mindful of memory usage.
Example: Avoiding Memory Leaks
let button = document.getElementById("btn");

const handleClick = () => console.log("Clicked!");


button.addEventListener("click", handleClick);

// Avoid memory leak:


button.removeEventListener("click", handleClick);

31.7 Summary & Next Steps


🎯 Key Takeaways
✔ Event Loop → Handles asynchronous operations non-blockingly.
✔ Callbacks → Used in event handling and async programming.
✔ Promises → Handle async operations better than callbacks.
✔ Async/Await → Makes async code look synchronous.
✔ Web Workers → Run JavaScript in parallel threads.
✔ Memory Management → Avoid memory leaks to optimize performance.

Chapter 32: JavaScript and Web APIs


JavaScript, when combined with Web APIs, enables developers to create powerful web applications
that interact with the browser, network, and local system. In this chapter, we’ll explore essential
Web APIs, including the Fetch API, WebSockets, IndexedDB, Local Storage, and Geolocation
API.

32.1 Understanding Web APIs


A Web API (Application Programming Interface) allows JavaScript to interact with the browser

📌
and external services.
Examples of Web APIs:
✔ DOM API → Manipulate web pages dynamically.
✔ Fetch API → Make network requests.
✔ WebSockets → Enable real-time communication.
✔ Local Storage & IndexedDB → Store data in the browser.
✔ Geolocation API → Access user location.

32.2 Fetch API: Making HTTP Requests


The Fetch API is used to send and receive data from servers asynchronously.

📌
Basic Fetch Request
Example: Fetching Data from an API
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error));

✔ Why?
• More powerful than XMLHttpRequest.
• Supports Promise-based syntax.

Fetching Data Using Async/Await


async function fetchData() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
let data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}

fetchData();

Sending Data with POST Request


fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "New Post",
body: "This is the content of the post",
userId: 1
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("Error:", error));

✔ Why?
• Used for form submissions, saving user data, etc.

32.3 WebSockets: Real-Time Communication


Unlike Fetch API, which works with request-response, WebSockets enable bi-directional real-
time communication.

WebSockets vs. HTTP Requests


Feature HTTP WebSockets
Communication Request-response Bi-directional
Speed Slower Faster
Feature HTTP WebSockets
Use Case Fetching data Real-time apps (chat, live updates)

📌
Creating a WebSocket Connection
Example: Client-Side WebSocket
const socket = new WebSocket("wss://example.com/socket");

socket.onopen = () => {
console.log("Connected to WebSocket");
socket.send("Hello Server!");
};

socket.onmessage = (event) => {


console.log("Received:", event.data);
};

socket.onerror = (error) => {


console.log("WebSocket Error:", error);
};

socket.onclose = () => {
console.log("WebSocket closed");
};

✔ Where is it used?
• Chat applications (WhatsApp, Messenger)
• Live stock market updates
• Online multiplayer games

32.4 Storing Data in the Browser: Local Storage & IndexedDB


JavaScript provides two primary ways to store data locally:

1. Local Storage (Simple Key-Value Storage)


• Stores data persistently in the browser (even after page reload).

📌
• Supports only strings (JSON can be used for objects).
Example: Storing and Retrieving Data
// Store data
localStorage.setItem("username", "Dhanian");

// Retrieve data
let user = localStorage.getItem("username");
console.log(user); // Output: Dhanian

// Remove data
localStorage.removeItem("username");

✔ Use Cases:
• Storing user preferences, themes, session data.
2. IndexedDB (For Large Data Storage)

📌
IndexedDB is a NoSQL database inside the browser, used for storing large datasets efficiently.
Example: Creating and Storing Data in IndexedDB
let db;
let request = indexedDB.open("MyDatabase", 1);

request.onsuccess = function (event) {


db = event.target.result;
console.log("Database opened!");
};

request.onupgradeneeded = function (event) {


let db = event.target.result;
let store = db.createObjectStore("users", { keyPath: "id" });
console.log("Database setup complete");
};

// Adding data
function addUser(id, name) {
let transaction = db.transaction("users", "readwrite");
let store = transaction.objectStore("users");
store.add({ id, name });
}

addUser(1, "Dhanian");

✔ Why IndexedDB?
• Handles large amounts of data (Unlike Local Storage).
• Supports indexed searching.

32.5 Geolocation API: Getting User Location

📌
The Geolocation API allows you to get the user's current location (if they grant permission).
Example: Getting Current Location
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
console.log("Latitude:", position.coords.latitude);
console.log("Longitude:", position.coords.longitude);
}, (error) => {
console.error("Error:", error.message);
});
} else {
console.log("Geolocation not supported!");
}

✔ Where is it used?
• Google Maps, Food Delivery Apps, Weather Apps.
32.6 Summary & Next Steps
🎯 Key Takeaways
✔ Fetch API → Used for making HTTP requests.
✔ WebSockets → Enable real-time communication.
✔ Local Storage → Store small data persistently.
✔ IndexedDB → Store large structured data in the browser.
✔ Geolocation API → Retrieve user location data.

Chapter 33: JavaScript Security Best Practices


Security is a crucial aspect of JavaScript development, especially when dealing with user data,
authentication, and API communication. This chapter will explore common security threats like
Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), CORS issues, and
Authentication Best Practices (JWT & OAuth).

33.1 Understanding JavaScript Security Risks


JavaScript, as a client-side language, is vulnerable to multiple security threats, especially in web
applications. Below are some common attack types:

Threat Description
Cross-Site Scripting (XSS) Injecting malicious scripts into web pages.
Cross-Site Request Forgery (CSRF) Forcing a user to perform unintended actions.
Clickjacking Embedding a site inside an iframe to trick users.
CORS Misconfiguration Allowing unauthorized domains to access APIs.
Exposed API Keys Hardcoding API keys in the frontend, leading to leaks.
Insecure Authentication Using weak or improper authentication methods.

33.2 Cross-Site Scripting (XSS) and Prevention


What is XSS?
Cross-Site Scripting (XSS) occurs when an attacker injects malicious JavaScript code into a web
page, which then executes in another user's browser.

Example of XSS Attack


<input type="text" id="commentBox" placeholder="Enter a comment">
<button onclick="submitComment()">Submit</button>
<div id="comments"></div>

<script>
function submitComment() {
let userInput = document.getElementById("commentBox").value;
document.getElementById("comments").innerHTML = "<p>" + userInput + "</p>";
}
</script>

🚨 Problem: If a user inputs:


<script>alert("Hacked!")</script>

The JavaScript will execute and run in the browser!

How to Prevent XSS?


1️⃣Use textContent Instead of innerHTML
document.getElementById("comments").textContent = userInput;

2️⃣Escape Special Characters


function sanitize(input) {
return input.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

3️⃣Use Content Security Policy (CSP)


Set a CSP header in your server response:
Content-Security-Policy: default-src 'self'; script-src 'self'

✔ Why? Blocks inline JavaScript execution.

33.3 Cross-Site Request Forgery (CSRF) and Prevention


What is CSRF?
A Cross-Site Request Forgery (CSRF) attack tricks a logged-in user into unknowingly performing

📌
an action on a site they are authenticated with.
Example of a CSRF Attack
If a user is logged into their bank account, an attacker can send them a link like:
<img src="https://bank.com/transfer?amount=5000&to=attacker_account">

✔ Result: The browser automatically includes cookies, making the request seem legitimate.

How to Prevent CSRF?


1️⃣Use CSRF Tokens
• Generate a CSRF token on the server.

📌
• Include the token in all forms.
Example:
<input type="hidden" name="csrf_token" value="random_generated_token">

2️⃣Use SameSite Cookies


Set-Cookie: session=xyz; SameSite=Strict

✔ Why? Prevents cookies from being sent with cross-origin requests.

33.4 CORS: Cross-Origin Resource Sharing


CORS (Cross-Origin Resource Sharing) controls which websites can access your APIs.

Common CORS Error


Access to fetch at 'https://api.example.com' from origin 'http://mywebsite.com'
has been blocked by CORS policy.

✔ Why? The API does not allow requests from http://mywebsite.com.

📌
How to Fix CORS Errors?
On the Server (Express.js Example)
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({ origin: "http://mywebsite.com" }));

app.listen(3000);

🚨
✔ Why? It explicitly allows only http://mywebsite.com to access the API.

❌ Security Warning:
Avoid using app.use(cors({ origin: "*" })) unless necessary, as it allows all
websites to access your API!

33.5 Secure Authentication: JWT & OAuth


JSON Web Token (JWT) Authentication

📌
JWT is a popular method of authentication, often used for APIs.
Example of a JWT Token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NTYiLCJpYXQiOjE2NjA0ODA
yNzh9.7T2_wKH1QaNhUaznI9-Uoz

✔ Structure:
• Header (Contains the algorithm)
• Payload (Contains user info)

📌
• Signature (Used for verification)
How to Generate a JWT Token in Node.js
const jwt = require("jsonwebtoken");

const user = { id: 123, username: "Dhanian" };


const token = jwt.sign(user, "secretKey", { expiresIn: "1h" });

console.log(token);

✔ Why? JWT allows secure, stateless authentication.

OAuth Authentication

📌
OAuth 2.0 is used for third-party authentication (Google, Facebook, GitHub).
Example OAuth Flow (Login with Google)
1️⃣User clicks "Login with Google".
2️⃣Redirects to Google OAuth page.
3️⃣User grants permission.
4️⃣Google sends a token to your backend.
5️⃣Backend verifies token and logs in the user.

33.6 Summary & Next Steps


🎯 Key Takeaways
✔ XSS Protection: Use textContent, CSP, and escaping characters.
✔ CSRF Prevention: Use CSRF tokens and SameSite cookies.
✔ Fixing CORS Issues: Configure server-side rules properly.
✔ Secure Authentication: Use JWT for APIs and OAuth for third-party logins.

Chapter 34: JavaScript Performance


Optimization
JavaScript performance plays a crucial role in improving user experience, reducing load times, and
ensuring smooth execution of applications. This chapter covers key techniques like lazy loading,
code splitting, web workers, debouncing, throttling, and memory optimization to make
JavaScript applications faster and more efficient.
34.1 Understanding JavaScript Performance Bottlenecks
Before optimizing, let’s identify common performance bottlenecks:

Issue Impact
Large JavaScript Bundles Slow page load times.
Blocking Operations in Main Thread Unresponsive UI, laggy interactions.
Memory Leaks Increased memory usage, leading to crashes.
Too Many Repaints & Reflows Poor rendering performance.
Unoptimized Loops & Event Listeners Increased CPU usage, slower execution.

34.2 Lazy Loading: Load Resources Only When Needed


Lazy loading ensures that JavaScript code, images, or components are loaded only when they are
needed, improving initial page speed.

Example: Lazy Loading Images


Instead of loading all images immediately, load them as they come into view.
<img src="placeholder.jpg" data-src="real-image.jpg" class="lazy-load">

document.addEventListener("DOMContentLoaded", function () {
const lazyImages = document.querySelectorAll(".lazy-load");

const observer = new IntersectionObserver((entries) => {


entries.forEach((entry) => {
if (entry.isIntersecting) {
let img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});

lazyImages.forEach((img) => observer.observe(img));


});

✔ Why? The real image is loaded only when visible on the screen, reducing initial page load time.

34.3 Code Splitting: Reduce JavaScript Bundle Size


Instead of loading a single large JavaScript file, code splitting breaks it into smaller chunks, loaded

📌
dynamically as needed.
Example with Webpack
Modify webpack.config.js to enable code splitting:
module.exports = {
optimization: {
splitChunks: {
chunks: "all",
},
},
};

📌
✔ Why? This prevents unnecessary JavaScript from loading until required.
Example in React (Dynamic Imports)
import React, { Suspense, lazy } from "react";

const HeavyComponent = lazy(() => import("./HeavyComponent"));

function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<HeavyComponent />
</Suspense>
);
}

✔ Why? The HeavyComponent is loaded only when needed, reducing initial bundle size.

34.4 Web Workers: Run JavaScript in Background Threads


JavaScript runs in a single-threaded environment, meaning heavy computations can block the UI.

📌
Web Workers offload tasks to background threads.
Example: Running Heavy Computation in a Web Worker

Step 1: Create a worker script (worker.js)


self.onmessage = function (event) {
let result = event.data * 2;
self.postMessage(result);
};

Step 2: Use the Worker in Main Thread


const worker = new Worker("worker.js");

worker.postMessage(10);

worker.onmessage = function (event) {


console.log("Result from worker:", event.data);
};

✔ Why? The computation happens without freezing the main thread, keeping the UI responsive.

34.5 Debouncing & Throttling: Optimize Event Handling


If a function executes too frequently, it can impact performance. Debouncing and throttling
optimize event listeners.
Debouncing: Delay Execution Until User Stops Input
Used for search bars, resizing windows, etc.
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}

window.addEventListener("resize", debounce(() => {


console.log("Window resized!");
}, 500));

✔ Why? Reduces the number of function calls, preventing lag.

Throttling: Execute Function at Fixed Intervals


Used for scroll events, dragging, etc.
function throttle(fn, limit) {
let lastCall = 0;
return function (...args) {
let now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn(...args);
}
};
}

window.addEventListener("scroll", throttle(() => {


console.log("User is scrolling...");
}, 1000));

✔ Why? Prevents event execution from happening too frequently.

34.6 Memory Optimization: Prevent Memory Leaks

📌
Memory leaks can slow down applications over time. Here’s how to prevent them:
Avoid Unused Global Variables
function test() {
let localVar = "I exist only inside this function";
}

📌
✔ Why? localVar is destroyed after function execution.

Remove Unused Event Listeners


const button = document.getElementById("clickBtn");

function handleClick() {
console.log("Button clicked");
}

button.addEventListener("click", handleClick);

// Remove event listener when no longer needed


button.removeEventListener("click", handleClick);

📌
✔ Why? Prevents unnecessary memory usage.
Use WeakMap for Object References
const cache = new WeakMap();

function storeData(obj, data) {


cache.set(obj, data);
}

let obj = {};


storeData(obj, "Some data");

// If `obj` is deleted, it will automatically be garbage collected

✔ Why? Prevents memory leaks by allowing automatic garbage collection.

34.7 Summary & Next Steps


🎯 Key Takeaways
✔ Lazy Loading: Load resources only when required.
✔ Code Splitting: Reduce initial bundle size for faster loading.
✔ Web Workers: Run expensive tasks in a background thread.
✔ Debouncing & Throttling: Optimize frequent event handling.
✔ Memory Optimization: Prevent memory leaks for better performance.

Chapter 35: JavaScript Testing & Debugging


Best Practices
Testing and debugging are essential in JavaScript development to ensure code reliability,
maintainability, and performance. This chapter covers unit testing, integration testing, debugging
techniques, and tools to help you write and maintain high-quality JavaScript code.

35.1 Why Testing & Debugging Matter in JavaScript


✅ Benefits of Testing & Debugging:
• Prevents Bugs – Reduces errors before they reach production.
• Improves Code Maintainability – Easy to update and scale projects.
• Enhances Performance – Identifies slow or memory-heavy code.
• Boosts Confidence – Ensures new changes don’t break existing functionality.
📌 Common Bugs in JavaScript:
Bug Type Example
Syntax Errors Missing semicolons, brackets, or typos.
Reference Errors Accessing undefined variables.
Type Errors Performing operations on incompatible types.
Logic Errors Incorrect conditions or calculations.

35.2 Debugging JavaScript Code


Debugging helps identify and fix issues in your JavaScript code.

🔎 Using console.log() for Debugging


A simple way to check variable values and execution flow:
let userName = "Dhanian";
console.log("User Name:", userName);

✔ Why? Helps track values in real-time during execution.

🛠 Using Browser Developer Tools


1. Open DevTools – Right-click on a webpage > Inspect > Console/Debugger.
2. Using the Debugger Tab – Add breakpoints to pause code execution.

📌
3. Monitoring Network Requests – Analyze API calls and performance in the "Network" tab.
Example: Using Breakpoints in Chrome DevTools
function calculateTotal(price, tax) {
let total = price + tax;
return total;
}
calculateTotal(100, 15);

✔ How?
1. Open Chrome DevTools > Go to Sources > Find your script.
2. Click next to the line number to set a breakpoint.
3. Refresh the page and inspect variable values step-by-step.

🛠 Using debugger Statement


The debugger keyword pauses JavaScript execution in the browser.
function sum(a, b) {
debugger; // Execution pauses here
return a + b;
}
sum(5, 10);
✔ Why? Allows step-by-step code execution for analysis.

35.3 JavaScript Testing Basics


🛠 Types of JavaScript Tests
Test Type Purpose
Unit Tests Tests small, isolated functions or components.
Integration Tests Checks interactions between modules/components.

📌
End-to-End (E2E) Tests Simulates real user behavior across the entire app.
Example: Testing a Function with Jest (Unit Test)
Jest is a popular JavaScript testing framework.

Step 1: Install Jest


npm install --save-dev jest

Step 2: Create a Function (math.js)


function add(a, b) {
return a + b;
}
module.exports = add;

Step 3: Write a Jest Test (math.test.js)


const add = require("./math");

test("adds 2 + 3 to equal 5", () => {


expect(add(2, 3)).toBe(5);
});

Step 4: Run Tests


npx jest

✔ Why? Ensures functions return expected results every time.

35.4 Testing Asynchronous Code


📌 Example: Testing an Async Function with Jest
function fetchUser() {
return new Promise((resolve) => {
setTimeout(() => resolve({ name: "Dhanian", age: 25 }), 1000);
});
}

test("fetches user data", async () => {


const user = await fetchUser();
expect(user.name).toBe("Dhanian");
});

✔ Why? Ensures API calls and async operations return expected results.

35.5 Integration Testing with Jest & Supertest


📌 Example: Testing an Express.js API Route

Step 1: Install Dependencies


npm install express supertest jest

Step 2: Create API (server.js)


const express = require("express");
const app = express();

app.get("/user", (req, res) => {


res.json({ name: "Dhanian", age: 25 });
});

module.exports = app;

Step 3: Write a Test (server.test.js)


const request = require("supertest");
const app = require("./server");

test("GET /user returns user data", async () => {


const res = await request(app).get("/user");
expect(res.body.name).toBe("Dhanian");
});

Step 4: Run the Test


npx jest

✔ Why? Confirms that API routes return correct responses.

35.6 End-to-End (E2E) Testing with Cypress


Cypress automates UI testing by simulating real user interactions.

📌 Example: Testing a Login Form

Step 1: Install Cypress


npm install cypress --save-dev

Step 2: Create a Cypress Test (cypress/integration/login.spec.js)


describe("Login Test", () => {
it("logs in successfully", () => {
cy.visit("http://localhost:3000");
cy.get("input[name='username']").type("Dhanian");
cy.get("input[name='password']").type("password123");
cy.get("button[type='submit']").click();
cy.url().should("include", "/dashboard");
});
});

Step 3: Run the Test


npx cypress open

✔ Why? Ensures the app’s login feature works as expected.

35.7 Summary & Next Steps


🎯 Key Takeaways
✔ Debugging Techniques – Use console.log(), breakpoints, and debugger.
✔ Unit Testing – Test small functions using Jest.
✔ Integration Testing – Validate API responses with Supertest.
✔ End-to-End Testing – Automate UI testing using Cypress.

Chapter 36: JavaScript Security Best Practices


Security is a critical aspect of JavaScript development, as vulnerabilities can lead to data breaches,
malicious attacks, and unauthorized access. In this chapter, we’ll explore common security
threats and best practices to keep your JavaScript applications secure.

36.1 Common Security Threats in JavaScript


🔴 1. Cross-Site Scripting (XSS)
Attackers inject malicious scripts into web pages to steal data or manipulate users.

📌 Example of XSS Attack


A vulnerable comment section:
<input type="text" id="comment">
<button onclick="postComment()">Post</button>
<div id="comments"></div>

<script>
function postComment() {
let comment = document.getElementById("comment").value;

❌ document.getElementById("comments").innerHTML += "<p>" + comment + "</p>"; //


Vulnerable
}
</script>

✔ Why is this dangerous?


An attacker can enter:
<script>alert('Hacked!');</script>


and execute harmful scripts in a user’s browser.
Fix: Escape User Input & Use innerText
document.getElementById("comments").innerText += comment; // ✅ Safe

🔴 2. Cross-Site Request Forgery (CSRF)


Attackers trick users into performing actions they didn’t intend to, such as submitting a form or
changing account details.

📌 Example of CSRF Attack


A user is logged into their banking website. The attacker sends them a malicious link:
<img src="https://bank.com/transfer?amount=1000&to=attacker" />


✔ If the user is already logged in, their browser automatically sends the request!
Fix: Use CSRF Tokens
Every request should include a unique, user-specific token:
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="randomGeneratedToken">
<input type="submit" value="Transfer Money">
</form>

🔴 3. SQL Injection
SQL injection happens when unsanitized inputs are used in SQL queries, allowing attackers to
manipulate databases.

📌 Example of Vulnerable Code (SQL Injection)


let username = req.query.username;
let query = "SELECT * FROM users WHERE username = '" + username + "'"; //
Vulnerable

An attacker can enter:
' OR '1'='1


which always returns true, allowing unauthorized access.
Fix: Use Parameterized Queries
let query = "SELECT * FROM users WHERE username = ?";
db.query(query, [username]); // ✅ Safe

36.2 Best Practices for Secure JavaScript


✅ 1. Avoid eval() & setTimeout() with User Input
The eval() function executes strings as code, making it extremely dangerous.

❌ Vulnerable Code

eval(userInput); // ❌
let userInput = "alert('Hacked!')";
Dangerous!

✅ Fix: Use Safer Alternatives


JSON.parse(userInput); // ✅ Safer for parsing JSON input

✅ 2. Use Content Security Policy (CSP)


CSP restricts which scripts can run on your site, preventing XSS attacks.
Example of a CSP Header
Set this in your HTTP headers:
Content-Security-Policy: default-src 'self'; script-src 'self'
https://trustedsource.com

✔ Allows scripts only from the same domain (self) and trusted sources.

✅ 3. Secure HTTP Headers with Helmet.js


helmet.js is a Node.js security middleware that sets secure HTTP headers automatically.

Install & Use Helmet


npm install helmet

const helmet = require("helmet");


app.use(helmet());

✔ Prevents Clickjacking, XSS, and more!

✅ 4. Encrypt User Passwords


Never store passwords as plain text!
Use bcrypt for Hashing Passwords
npm install bcrypt
const bcrypt = require("bcrypt");
let hashedPassword = await bcrypt.hash("userPassword123", 10);

✔ Ensures passwords remain secure even if the database is compromised.

✅ 5. Validate & Sanitize User Input


Use libraries like express-validator to ensure inputs are safe.

Install & Use express-validator


npm install express-validator

const { body, validationResult } = require("express-validator");

app.post("/register", [
body("email").isEmail().withMessage("Invalid email format"),
body("password").isLength({ min: 8 }).withMessage("Password too short")
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
});

✔ Prevents SQL Injection, XSS, and invalid data.

36.3 Secure API Authentication


✅ Use JWT (JSON Web Tokens) for Authentication
JWTs ensure secure authentication without storing session data on the server.

📌

Example: Secure Login with JWT
Install Dependencies
npm install jsonwebtoken

✅ Generate a Token
const jwt = require("jsonwebtoken");

console.log(token); // ✅
let token = jwt.sign({ userId: 123 }, "secretKey", { expiresIn: "1h" });
Secure token

✅ Verify Token in API Requests


function authenticate(req, res, next) {
let token = req.headers.authorization;
if (!token) return res.status(403).send("Access denied");

try {
let decoded = jwt.verify(token, "secretKey");
req.user = decoded;
next();
} catch (err) {
res.status(400).send("Invalid token");
}
}

✔ Only authenticated users can access protected routes.

36.4 Summary & Next Steps


🎯 Key Takeaways
✔ Prevent XSS – Sanitize user input and use CSP.
✔ Prevent CSRF – Implement CSRF tokens for critical actions.
✔ Secure Databases – Use parameterized queries to prevent SQL Injection.
✔ Secure APIs – Use JWTs for authentication.
✔ Use Security Libraries – Helmet.js, bcrypt, express-validator for added security.

Chapter 37: JavaScript Performance


Optimization
In modern web development, performance is critical for user experience, SEO, and efficiency. A
slow JavaScript application can lead to high bounce rates, poor conversion rates, and low user
engagement. In this chapter, we’ll explore JavaScript performance bottlenecks and best
practices to optimize speed and efficiency.

37.1 Why JavaScript Performance Matters


🔹
🔹 Fast-loading websites improve user engagement.

🔹 Google ranks faster websites higher in search results.


Optimized JavaScript reduces memory usage, preventing slow UI interactions.

37.2 Common JavaScript Performance Issues


🔴 1. Blocking the Main Thread
JavaScript runs in a single-threaded environment, meaning long-running tasks can freeze the UI.

📌 Example of a Blocking Operation


function blockUI() {
let start = Date.now();

}
// ❌
while (Date.now() - start < 5000) {
Blocks everything for 5 seconds
}
blockUI();
console.log("Done"); // This will only run AFTER 5 seconds!

✔ Fix: Use setTimeout() or Web Workers


setTimeout(() => {
console.log("Task executed asynchronously");
}, 0);

🔴 2. Excessive DOM Manipulation


Directly updating the DOM frequently slows performance.

📌 Inefficient DOM Manipulation


for (let i = 0; i < 1000; i++) {
let div = document.createElement("div");

}
div.innerText = `Item ${i}`;
document.body.appendChild(div); // ❌
Multiple reflows

✔ Fix: Use Document Fragment


let fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
let div = document.createElement("div");
div.innerText = `Item ${i}`;
fragment.appendChild(div);
}
document.body.appendChild(fragment); // ✅
Single reflow

🔴 3. Inefficient Event Listeners


Adding too many event listeners slows down interactions.

📌 Example: Adding Listeners in a Loop


const buttons = document.querySelectorAll("button");
buttons.forEach((button) => {
button.addEventListener("click", () => console.log("Clicked")); //
Inefficient

});

✔ Fix: Use Event Delegation


document.body.addEventListener("click", (event) => {
if (event.target.tagName === "BUTTON") {
console.log("Clicked");
}
});

✔ Improves performance by handling all clicks with a single event listener!


37.3 Best Practices for JavaScript Performance
✅ 1. Use Asynchronous Code (async / await)
Blocking requests slow down applications. Use async/await for better responsiveness.

📌 Example: Fetch Data Efficiently


async function fetchData() {
let response = await fetch("https://api.example.com/data");
let data = await response.json();
console.log(data);
}
fetchData();

✔ Prevents UI blocking & ensures better performance.

✅ 2. Minify & Compress JavaScript


Minification removes unnecessary characters, reducing file size.

📌 Tools for Minification


• UglifyJS
• Terser


• Google Closure Compiler
Example: Minify a file using Terser
npx terser script.js -o script.min.js

✔ Reduces file size & improves loading speed.

✅ 3. Use Lazy Loading


Lazy loading defers loading non-critical resources until needed.

📌 Example: Lazy Load Images


<img data-src="image.jpg" class="lazy-load">

document.addEventListener("DOMContentLoaded", () => {
let images = document.querySelectorAll(".lazy-load");
images.forEach((img) => {
img.src = img.dataset.src;
});
});

✔ Speeds up page load times by loading assets only when necessary.


✅ 4. Optimize Loops
Loops can be expensive. Optimize them to improve speed.

📌 Example: Inefficient Loop


let arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}

✔ Fix: Use forEach() or map()


arr.forEach(item => console.log(item));

✔ Modern JavaScript methods are optimized for speed!

✅ 5. Reduce Memory Leaks


Memory leaks cause applications to slow down over time.

📌 Common Causes of Memory Leaks


1. Uncleared Timers & Intervals

clearInterval(interval); // ✅
let interval = setInterval(() => console.log("Running..."), 1000);
Always clear intervals

2. Detached DOM Elements


let div = document.getElementById("myDiv");

div = null; // ✅
document.body.removeChild(div);
Remove references to avoid memory leaks

✅ 6. Optimize API Calls with Debouncing & Throttling


Making too many API calls overloads servers & slows performance.

📌 Debounce Example (Waits Before Executing)


function debounce(func, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}

window.addEventListener("resize", debounce(() => {


console.log("Window resized!");
}, 300));

✔ Useful for search inputs & window resize events!


37.4 Summary & Next Steps
🎯 Key Takeaways
✔ Avoid Blocking Code – Use setTimeout(), Web Workers, and async/await.
✔ Optimize DOM Manipulation – Use Document Fragments & Event Delegation.
✔ Minify & Compress JS – Use Terser or UglifyJS.
✔ Use Lazy Loading – Load assets only when needed.
✔ Optimize Loops – Use .forEach(), .map(), and efficient algorithms.
✔ Prevent Memory Leaks – Clear timers & remove unused DOM references.

Chapter 38: JavaScript Debugging & Error


Handling
Debugging is an essential skill for JavaScript developers. Even the best-written code can have bugs
and unexpected errors. In this chapter, we’ll cover common JavaScript errors, debugging
techniques, and best practices to write error-free, maintainable code.

38.1 Understanding JavaScript Errors


JavaScript errors occur at runtime or during execution. They can be classified as:
1. Syntax Errors – Mistakes in code structure (e.g., missing brackets).
2. Reference Errors – Using an undefined variable.
3. Type Errors – Using the wrong data type (e.g., calling a function on undefined).
4. Range Errors – Exceeding allowed limits (e.g., too many function calls in recursion).
5. Logical Errors – Code runs but produces incorrect results.

38.2 Common JavaScript Errors & Fixes


🔴 1. Syntax Errors
Occurs when the JavaScript interpreter cannot understand the code.

📌 Example: Missing Bracket


function greet(name {
console.log("Hello " + name);
}

❌ Error: SyntaxError: Unexpected token '{'

✔ Fix: Ensure brackets are closed properly


function greet(name) {
console.log("Hello " + name);
}

🔴 2. Reference Errors
Occurs when trying to access a variable that does not exist.

📌

Example: Using an Undefined Variable
console.log(age); // ReferenceError: age is not defined

✔ Fix: Declare the variable before using it


let age = 25;
console.log(age);

🔴 3. Type Errors
Occurs when performing an operation on the wrong data type.

📌 Example: Calling a Function on undefined


let person;
console.log(person.toUpperCase()); //
undefined
❌ TypeError: Cannot read properties of

✔ Fix: Check if the variable exists before using it


if (person) {
console.log(person.toUpperCase());
}

🔴 4. Range Errors
Occurs when a function is called with values outside its expected range.

📌 Example: Infinite Recursion


function countDown(n) {

}
console.log(n);
countDown(n - 1); // ❌ Maximum call stack size exceeded

countDown(10);

✔ Fix: Add a base condition to stop recursion


function countDown(n) {
if (n <= 0) return; //
console.log(n);
✅ Base case

countDown(n - 1);
}
countDown(10);
38.3 Debugging JavaScript with Console & DevTools
✅ 1. Using console.log() for Debugging
console.log() is the most common debugging tool.

📌 Example: Checking Variable Values


let user = "Dhanian";
console.log("User:", user); // ✅ Logs value to console

✅ 2. Using console.error() for Errors


Use console.error() to highlight errors in red.
console.error("This is an error message!");

✅ 3. Using console.table() for Objects & Arrays


console.table() displays structured data in a table format.
let users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
];
console.table(users);

✅ 4. Using debugger to Pause Code Execution


The debugger statement pauses execution and opens DevTools.
let num = 5;
debugger; // ✅Execution stops here in browser DevTools
console.log(num);

✔ You can inspect variables in the browser's Developer Console.

38.4 Handling Errors with Try...Catch


The try...catch block prevents program crashes by handling errors gracefully.

📌 Example: Handling Errors in a Function


try {
let result = JSON.parse("invalid json");
} catch (error) {
console.error("Error parsing JSON:", error.message);
}

✔ If an error occurs, execution continues without breaking the app.


38.5 Using throw to Create Custom Errors
You can use throw to create custom error messages.

📌 Example: Throwing an Error When Input is Invalid


function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero!");
}
return a / b;
}

try {
console.log(divide(10, 0));
} catch (error) {

}
console.error(error.message); // ❌ "Cannot divide by zero!"

38.6 Using finally for Cleanup Code


The finally block runs whether an error occurs or not.

📌 Example: Ensuring a File Closes After Use


function readFile() {
try {
console.log("Reading file...");
throw new Error("File not found!");
} catch (error) {
console.error("Error:", error.message);
} finally {
console.log("Closing file...");
}
}
readFile();

✔ Useful for database connections, file operations, or cleanup tasks.

38.7 Using onerror for Global Error Handling


The window.onerror method catches all uncaught errors in a script.
window.onerror = function (message, source, lineno, colno, error) {
console.error("Global Error:", message, "at", source, ":", lineno);
};

✔ Prevents crashes & logs issues for debugging.


38.8 Debugging with Browser DevTools
Most browsers have built-in debugging tools:
✔ Google Chrome: Ctrl + Shift + I → Go to "Console" or "Sources" tab.
✔ Firefox: F12 → Open "Debugger" tab.
✔ Edge/Safari: F12 → Use DevTools for debugging JavaScript.

🔹 Breakpoints
Pause execution on a specific line to inspect variable values.

🔹 Network Tab
Check API responses & failed requests for debugging.

38.9 Summary & Next Steps


🎯 Key Takeaways
✔ Understand JavaScript Errors – Syntax, Reference, Type, Range, and Logical Errors.
✔ Use console.log(), console.table(), and debugger for debugging.
✔ Use try...catch to handle errors & throw for custom errors.
✔ Use finally for cleanup tasks.
✔ Use window.onerror for global error handling.
✔ Use DevTools to set breakpoints & inspect performance.

Chapter 39: Advanced JavaScript Concepts


Now that we have covered debugging and error handling, it’s time to dive into advanced
JavaScript concepts that will make you a more proficient developer. In this chapter, we will
explore closures, hoisting, prototypes, the this keyword, event loops, and asynchronous
programming. These concepts are fundamental to mastering JavaScript and writing efficient code.

39.1 Closures in JavaScript


A closure is a function that remembers the variables from its lexical scope, even after the outer
function has finished executing.

📌 Example: Closure Retaining Access to Outer Variables


function outerFunction(outerValue) {
return function innerFunction(innerValue) {
console.log(`Outer: ${outerValue}, Inner: ${innerValue}`);
};
}
closureExample("World"); // ✅
const closureExample = outerFunction("Hello");
Output: Outer: Hello, Inner: World

✔ The inner function remembers outerValue even after outerFunction has executed.

🎯 Practical Use Case: Data Privacy with Closures


Closures help in data encapsulation by preventing direct access to variables.
function counter() {
let count = 0;
return {
increment: function () {
count++;
console.log("Count:", count);
},
decrement: function () {
count--;
console.log("Count:", count);
},
};
}

const myCounter = counter();


myCounter.increment(); // ✅✅
Count: 1
myCounter.increment(); //
myCounter.decrement(); // ✅
Count: 2
Count: 1

✔ count is private and cannot be modified directly outside the function.

39.2 Hoisting in JavaScript


Hoisting is JavaScript’s behavior of moving declarations to the top of their scope.

🔴 Hoisting with var


console.log(myVar); // ✅ Output: undefined
var myVar = 10;

✔ Only declarations are hoisted, not initializations.

✅ Hoisting with Functions


greet(); // ✅ Output: Hello, World!

function greet() {
console.log("Hello, World!");
}

✔ Function declarations are fully hoisted, so they can be called before they are defined.

❌ Hoisting with let and const (Temporal Dead Zone)


console.log(name); // ❌ ReferenceError: Cannot access 'name' before
initialization
let name = "Dhanian";
✔ let and const are hoisted but remain uninitialized in a temporal dead zone until execution
reaches them.

39.3 Understanding the this Keyword


The this keyword refers to the object that is executing the function.

🔹 1. this in Global Scope


console.log(this); // ✅ Refers to `window` object in browsers

🔹 2. this in an Object Method


const person = {
name: "Dhanian",
greet: function () {
console.log(`Hello, my name is ${this.name}`);
},
};
person.greet(); // ✅
Output: Hello, my name is Dhanian

✔ this refers to the calling object (person).

🔹 3. this in Arrow Functions (Lexical this)


const obj = {
name: "Dhanian",
greet: () => {
console.log(`Hello, my name is ${this.name}`);
},
};
obj.greet(); // ❌Output: Hello, my name is undefined

✔ Arrow functions do not have their own this; they inherit this from their enclosing scope.

🔹 4. this in Event Listeners

console.log(this); //
});

document.getElementById("btn").addEventListener("click", function () {
Refers to the button element

✔ Inside event listeners, this refers to the element that triggered the event.

39.4 Prototypes & Prototypal Inheritance


Every JavaScript object has a prototype, which allows objects to inherit properties and methods.

📌 Example: Adding Methods Using Prototypes


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

dhanian.sayHello(); // ✅
const dhanian = new Person("Dhanian");
Output: Hello, my name is Dhanian

✔ The sayHello method is shared among all instances of Person.

39.5 Event Loop & Asynchronous JavaScript


JavaScript is single-threaded but can handle asynchronous tasks using the event loop.

🔹 Event Loop in Action


console.log("Start");

setTimeout(() => {
console.log("Timeout callback");
}, 0);

console.log("End");

✔ Output:
Start
End
Timeout callback

Even though setTimeout has 0ms delay, it is placed in the event queue and executed last.

39.6 Asynchronous JavaScript with Promises & Async/Await


Promises allow handling of asynchronous operations more cleanly.

📌 Using Promises
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => resolve("Data received"), 2000);
});

fetchData.then((data) => console.log(data)); //


received
✅ Output after 2 sec: Data

📌 Using async/await for Cleaner Code


async function getData() {
const data = await fetchData;
console.log(data);
}
getData();

✔ async/await makes asynchronous code look synchronous and is easier to read.


39.7 Summary & Next Steps
🎯 Key Takeaways
✔ Closures allow functions to remember their lexical scope.
✔ Hoisting moves declarations to the top of their scope.
✔ this keyword changes based on how a function is called.
✔ Prototypes allow object inheritance using a shared prototype chain.
✔ Event Loop manages asynchronous operations in JavaScript.
✔ Promises & async/await simplify handling of asynchronous tasks.

Conclusion: Mastering JavaScript


Congratulations on reaching the end of this JavaScript journey! 🎉 Throughout this book, we've
explored JavaScript from the basics to advanced topics, covering everything you need to become a
proficient JavaScript developer.


By now, you should be comfortable with:

✅ JavaScript fundamentals (variables, functions, objects, arrays)



DOM manipulation and event handling
ES6+ features like arrow functions, destructuring, and modules


Asynchronous programming with Promises and async/await

✅ Advanced concepts like closures, prototypes, and the event loop


Debugging, optimization, and best coding practices
JavaScript is a powerful language that continues to evolve. The key to mastering it is consistent
practice, building projects, and staying updated with the latest advancements in the JavaScript
ecosystem.

Stay Connected & Support My Work

📌
If you found this book helpful, I’d love to stay connected with you!

📌 Follow me on X (Twitter): @e_opore


Support my work on Ko-fi: ko-fi.com/codewithdhanian
Your support helps me create more high-quality content for developers like you.
Now go out there and build amazing things with JavaScript!

You might also like