Complete JavaScript Guide: From Arrays to Advanced Concepts
1. Arrays
Arrays are ordered collections of elements that can hold any data type. They're
zero-indexed and dynamic in JavaScript.
Critical Analysis
JavaScript uses prototype chain for inheritance, not classical inheritance
Every object has a prototype (except Object.prototype)
Understanding __proto__ vs prototype property is crucial
Prototype pollution is a security concern
Modern classes are syntactic sugar over prototypal inheritance
Step-by-Step Example
javascript// 1. Understanding prototypes
function Person(name, age) {
this.name = name;
this.age = age;
}
// Adding methods to prototype
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
Person.prototype.getAgeInYears = function(targetYear) {
return targetYear - (new Date().getFullYear() - this.age);
};
const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);
console.log(person1.greet()); // "Hello, I'm Alice"
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
// 2. Prototype chain exploration
console.log('Prototype chain for person1:');
console.log('person1.__proto__:', person1.__proto__ === Person.prototype);
console.log('Person.prototype.__proto__:', Person.prototype.__proto__ ===
Object.prototype);
console.log('Object.prototype.__proto__:', Object.prototype.__proto__ === null);
// 3. Inheritance with prototypes
function Employee(name, age, jobTitle, salary) {
// Call parent constructor
Person.call(this, name, age);
this.jobTitle = jobTitle;
this.salary = salary;
}
// Set up inheritance
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
// Add Employee-specific methods
Employee.prototype.work = function() {
return `${this.name} is working as a ${this.jobTitle}`;
};
Employee.prototype.getAnnualSalary = function() {
return this.salary * 12;
};
// Override parent method
Employee.prototype.greet = function() {
return `Hello, I'm ${this.name}, a ${this.jobTitle}`;
};
const employee = new Employee('Charlie', 28, 'Developer', 5000);
console.log(employee.greet()); // "Hello, I'm Charlie, a Developer"
console.log(employee.work()); // "Charlie is working as a Developer"
console.log(employee instanceof Employee); // true
console.log(employee instanceof Person); // true
// 4. Modern prototype manipulation
const animal = {
type: 'Unknown',
makeSound() {
return `The ${this.type} makes a sound`;
},
eat(food) {
return `The ${this.type} eats ${food}`;
}
};
// Create objects with specific prototype
const dog = Object.create(animal);
dog.type = 'Dog';
dog.breed = 'Golden Retriever';
dog.makeSound = function() {
return `
- Classes are syntactic sugar over JavaScript's prototypal inheritance
- Provide cleaner syntax but don't change underlying prototype behavior
- Static methods belong to the class, not instances
- Private fields (#field) are truly private, unlike conventions (_field)
### Step-by-Step Example
```javascript
// 1. Basic class definition
class Vehicle {
// Private field (ES2022)
#engineStatus = false;
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.mileage = 0;
}
// Instance method
start() {
if (this.#engineStatus) {
return 'Vehicle is already running';
}
this.#engineStatus = true;
return `${this.make} ${this.model} started`;
}
stop() {
this.#engineStatus = false;
return `${this.make} ${this.model} stopped`;
}
// Getter
get isRunning() {
return this.#engineStatus;
}
// Setter
set currentMileage(miles) {
if (miles >= this.mileage) {
this.mileage = miles;
} else {
throw new Error('Mileage cannot decrease');
}
}
// Static method
static compareAge(vehicle1, vehicle2) {
return vehicle1.year - vehicle2.year;
}
// Static property
static manufacturer = 'Generic Motors';
}
// 2. Class inheritance
class Car extends Vehicle {
constructor(make, model, year, doors) {
super(make, model, year); // Call parent constructor
this.doors = doors;
this.passengers = 0;
}
// Override parent method
start() {
const result = super.start(); // Call parent method
return `${result} - Car ready for passengers`;
}
// New method specific to Car
addPassenger(count = 1) {
const maxCapacity = this.doors === 2 ? 4 : 5;
if (this.passengers + count <= maxCapacity) {
this.passengers += count;
return `Added ${count} passenger(s). Total: ${this.passengers}`;
}
throw new Error('Not enough seats');
}
}
// 3. Usage examples
const myCar = new Car('Toyota', 'Camry', 2020, 4);
console.log(myCar.start()); // Toyota Camry started - Car ready for passengers
console.log(myCar.isRunning); // true
myCar.currentMileage = 15000;
console.log(myCar.addPassenger(2)); // Added 2 passenger(s). Total: 2
// Static method usage
const otherCar = new Car('Honda', 'Civic', 2018, 4);
console.log(Vehicle.compareAge(myCar, otherCar)); // 2
// 4. Abstract-like patterns (no native abstract classes)
class Shape {
constructor(name) {
if (this.constructor === Shape) {
throw new Error('Cannot instantiate abstract class');
}
this.name = name;
}
// "Abstract" method - must be implemented by subclasses
area() {
throw new Error('area() method must be implemented');
}
describe() {
return `This is a ${this.name} with area ${this.area()}`;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super('Rectangle');
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
class Circle extends Shape {
constructor(radius) {
super('Circle');
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
// 5. Mixin pattern for multiple inheritance
const Flyable = {
fly() {
return `${this.name} is flying`;
},
land() {
return `${this.name} has landed`;
}
};
const Swimmable = {
swim() {
return `${this.name} is swimming`;
},
dive() {
return `${this.name} is diving`;
}
};
class Duck {
constructor(name) {
this.name = name;
}
}
// Apply mixins
Object.assign(Duck.prototype, Flyable, Swimmable);
const duck = new Duck('Donald');
console.log(duck.fly()); // Donald is flying
console.log(duck.swim()); // Donald is swimming
13. Callbacks
Callbacks are functions passed as arguments to other functions, executed at a later
time.
Critical Analysis
Foundation of asynchronous JavaScript before Promises
Can lead to "callback hell" with nested operations
Error handling requires careful parameter conventions
Understanding execution context and timing is crucial
Step-by-Step Example
javascript// 1. Basic callback pattern
function fetchData(callback) {
// Simulate async operation
setTimeout(() => {
const data = {id: 1, name: 'John Doe'};
callback(null, data); // Node.js convention: (error, result)
}, 1000);
}
function handleData(error, data) {
if (error) {
console.error('Error:', error);
return;
}
console.log('Received data:', data);
}
fetchData(handleData);
// 2. Array methods with callbacks
const numbers = [1, 2, 3, 4, 5];
// forEach callback
numbers.forEach((num, index, array) => {
console.log(`Index ${index}: ${num}`);
});
// Custom callback implementation
function processArray(array, callback) {
const results = [];
for (let i = 0; i < array.length; i++) {
results.push(callback(array[i], i, array));
}
return results;
}
const doubled = processArray(numbers, num => num * 2);
// 3. Event-driven callbacks
function createEventEmitter() {
const events = {};
return {
on(event, callback) {
if (!events[event]) {
events[event] = [];
}
events[event].push(callback);
},
emit(event, data) {
if (events[event]) {
events[event].forEach(callback => callback(data));
}
},
off(event, callback) {
if (events[event]) {
events[event] = events[event].filter(cb => cb !== callback);
}
}
};
}
const emitter = createEventEmitter();
// Register callbacks
emitter.on('userLogin', (user) => {
console.log(`User ${user.name} logged in`);
});
emitter.on('userLogin', (user) => {
console.log(`Welcome back, ${user.name}!`);
});
// Trigger callbacks
emitter.emit('userLogin', {name: 'Alice', id: 123});
// 4. Callback hell example and solution
// Bad: Nested callbacks (callback hell)
function badAsyncFlow() {
fetchUser(1, (userError, user) => {
if (userError) throw userError;
fetchUserPosts(user.id, (postsError, posts) => {
if (postsError) throw postsError;
fetchPostComments(posts[0].id, (commentsError, comments) => {
if (commentsError) throw commentsError;
console.log('Final result:', {user, posts, comments});
});
});
});
}
// Better: Named functions to flatten structure
function handleComments(user, posts) {
return (commentsError, comments) => {
if (commentsError) throw commentsError;
console.log('Final result:', {user, posts, comments});
};
}
function handlePosts(user) {
return (postsError, posts) => {
if (postsError) throw postsError;
fetchPostComments(posts[0].id, handleComments(user, posts));
};
}
function handleUser(userError, user) {
if (userError) throw userError;
fetchUserPosts(user.id, handlePosts(user));
}
function betterAsyncFlow() {
fetchUser(1, handleUser);
}
// 5. Higher-order functions with callbacks
function createRetry(maxAttempts) {
return function retry(operation, callback) {
let attempts = 0;
function attempt() {
attempts++;
operation((error, result) => {
if (error && attempts < maxAttempts) {
console.log(`Attempt ${attempts} failed, retrying...`);
setTimeout(attempt, 1000 * attempts); // Exponential backoff
} else {
callback(error, result);
}
});
}
attempt();
};
}
const retryThreeTimes = createRetry(3);
// 6. Callback with context binding
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.requestCount = 0;
}
makeRequest(endpoint, callback) {
this.requestCount++;
console.log(`Making request ${this.requestCount} to ${this.baseUrl}$
{endpoint}`);
// Simulate API call
setTimeout(() => {
callback(null, {data: 'API response', requestId: this.requestCount});
}, 500);
}
// Method that uses callback with proper context
get(endpoint, callback) {
// Ensure 'this' context is preserved
this.makeRequest(endpoint, (error, result) => {
if (error) {
callback(error);
return;
}
callback(null, {
...result,
timestamp: new Date(),
baseUrl: this.baseUrl
});
});
}
}
// Helper functions for examples
function fetchUser(id, callback) {
setTimeout(() => callback(null, {id, name: 'John'}), 100);
}
function fetchUserPosts(userId, callback) {
setTimeout(() => callback(null, [{id: 1, title: 'Post 1'}]), 100);
}
function fetchPostComments(postId, callback) {
setTimeout(() => callback(null, [{id: 1, text: 'Great post!'}]), 100);
}
14. Asynchronous Logic
Asynchronous programming handles operations that don't complete immediately.
Critical Analysis
JavaScript's single-threaded nature requires async patterns
Event loop manages execution order of async operations
Promises provide better error handling than callbacks
Understanding micro/macro task queues is essential for timing
Step-by-Step Example
javascript// 1. Understanding the Event Loop
console.log('1. Synchronous start');
setTimeout(() => {
console.log('4. setTimeout (macro task)');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise (micro task)');
});
console.log('2. Synchronous end');
// Output order: 1, 2, 3, 4
// 2. Callback-based async operations
function asyncOperation(data, callback) {
// Simulate network delay
setTimeout(() => {
if (Math.random() > 0.2) {
callback(null, `Processed: ${data}`);
} else {
callback(new Error('Operation failed'), null);
}
}, Math.random() * 1000);
}
// Usage with callbacks
asyncOperation('test data', (error, result) => {
if (error) {
console.error('Error:', error.message);
} else {
console.log('Success:', result);
}
});
// 3. Promise-based approach
function promiseBasedOperation(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.2) {
resolve(`Processed: ${data}`);
} else {
reject(new Error('Operation failed'));
}
}, Math.random() * 1000);
});
}
// Usage with Promises
promiseBasedOperation('test data')
.then(result => console.log('Success:', result))
.catch(error => console.error('Error:', error.message));
// 4. Multiple async operations
const operations = [
promiseBasedOperation('data 1'),
promiseBasedOperation('data 2'),
promiseBasedOperation('data 3')
];
// All operations in parallel
Promise.all(operations)
.then(results => {
console.log('All operations completed:', results);
})
.catch(error => {
console.error('One or more operations failed:', error.message);
});
// First successful operation
Promise.race(operations)
.then(result => {
console.log('First completed:', result);
})
.catch(error => {
console.error('All operations failed or first failed:', error.message);
});
// 5. Sequential async operations
function sequentialOperations() {
return promiseBasedOperation('step 1')
.then(result1 => {
console.log('Step 1 completed:', result1);
return promiseBasedOperation('step 2');
})
.then(result2 => {
console.log('Step 2 completed:', result2);
return promiseBasedOperation('step 3');
})
.then(result3 => {
console.log('Step 3 completed:', result3);
return 'All steps completed successfully';
})
.catch(error => {
console.error('Sequential operation failed:', error.message);
throw error;
});
}
// 6. Async/await pattern (modern approach)
async function modernAsyncFlow() {
try {
console.log('Starting async flow...');
const result1 = await promiseBasedOperation('modern step 1');
console.log('Modern step 1:', result1);
const result2 = await promiseBasedOperation('modern step 2');
console.log('Modern step 2:', result2);
// Parallel operations with async/await
const [result3, result4] = await Promise.all([
promiseBasedOperation('parallel 1'),
promiseBasedOperation('parallel 2')
]);
console.log('Parallel results:', {result3, result4});
return 'Modern async flow completed';
} catch (error) {
console.error('Modern async flow failed:', error.message);
throw error;
}
}
// 7. Error handling patterns
async function robustAsyncOperation() {
const maxRetries = 3;
let attempts = 0;
while (attempts < maxRetries) {
try {
const result = await promiseBasedOperation(`attempt ${attempts + 1}`);
return result;
} catch (error) {
attempts++;
console.log(`Attempt ${attempts} failed:`, error.message);
if (attempts >= maxRetries) {
throw new Error(`Failed after ${maxRetries} attempts: $
{error.message}`);
}
// Wait before retry with exponential backoff
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempts)
* 1000));
}
}
}
// 8. Async generators for streaming data
async function* asyncDataGenerator() {
for (let i = 1; i <= 5; i++) {
// Simulate async data fetching
await new Promise(resolve => setTimeout(resolve, 500));
yield `Data chunk ${i}`;
}
}
async function consumeAsyncGenerator() {
for await (const chunk of asyncDataGenerator()) {
console.log('Received:', chunk);
}
}
// 9. Real-world example: API data processing
class DataProcessor {
constructor(apiUrl) {
this.apiUrl = apiUrl;
this.cache = new Map();
}
async fetchWithCache(endpoint) {
if (this.cache.has(endpoint)) {
console.log('Cache hit for:', endpoint);
return this.cache.get(endpoint);
}
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 500));
const data = {endpoint, data: `Response for ${endpoint}`, timestamp:
Date.now()};
this.cache.set(endpoint, data);
console.log('Cached data for:', endpoint);
return data;
} catch (error) {
console.error(`Failed to fetch ${endpoint}:`, error.message);
throw error;
}
}
async processMultipleEndpoints(endpoints) {
const results = await Promise.allSettled(
endpoints.map(endpoint => this.fetchWithCache(endpoint))
);
const successful = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failed = results
.filter(result => result.status === 'rejected')
.map(result => result.reason.message);
return {successful, failed};
}
}
// Usage example
async function demonstrateAsyncPatterns() {
try {
console.log('=== Sequential Operations ===');
await sequentialOperations();
console.log('\n=== Modern Async Flow ===');
await modernAsyncFlow();
console.log('\n=== Robust Operation with Retries ===');
await robustAsyncOperation();
console.log('\n=== Async Generator ===');
await consumeAsyncGenerator();
console.log('\n=== Data Processing ===');
const processor = new DataProcessor('https://api.example.com');
const result = await processor.processMultipleEndpoints([
'/users', '/posts', '/comments'
]);
console.log('Processing result:', result);
} catch (error) {
console.error('Demo failed:', error.message);
}
}
15. Advanced Classes
Advanced class features including inheritance, mixins, and design patterns.
Critical Analysis
Class fields and private methods provide true encapsulation
Inheritance chains can become complex and hard to maintain
Composition over inheritance principle applies
Design patterns help structure complex class hierarchies
Step-by-Step Example
javascript// 1. Advanced class features (ES2022+)
class BankAccount {
// Private fields
#balance = 0;
#accountNumber;
#transactions = [];
// Static private field
static #nextAccountNumber = 1000;
constructor(initialDeposit = 0, accountHolder) {
this.#accountNumber = BankAccount.#generateAccountNumber();
this.accountHolder = accountHolder;
this.createdAt = new Date();
if (initialDeposit > 0) {
this.#balance = initialDeposit;
this.#addTransaction('deposit', initialDeposit, 'Initial deposit');
}
}
// Private method
#addTransaction(type, amount, description) {
this.#transactions.push({
id: Date.now(),
type,
amount,
description,
timestamp: new Date(),
balance: this.#balance
});
}
// Static private method
static #generateAccountNumber() {
return this.#nextAccountNumber++;
}
// Public methods
deposit(amount) {
if (amount <= 0) {
throw new Error('Deposit amount must be positive');
}
this.#balance += amount;
this.#addTransaction('deposit', amount, 'Deposit');
return this.#balance;
}
withdraw(amount) {
if (amount <= 0) {
throw new Error('Withdrawal amount must be positive');
}
if (amount > this.#balance) {
throw new Error('Insufficient funds');
}
this.#balance -= amount;
this.#addTransaction('withdrawal', amount, 'Withdrawal');
return this.#balance;
}
// Getter for private field
get balance() {
return this.#balance;
}
get accountNumber() {
return this.#accountNumber;
}
getTransactionHistory() {
// Return copy to prevent external modification
return [...this.#transactions];
}
// Static method
static compareBalances(account1, account2) {
return account1.balance - account2.balance;
}
}
// 2. Inheritance with advanced features
class SavingsAccount extends BankAccount {
#interestRate;
#minimumBalance;
constructor(initialDeposit, accountHolder, interestRate = 0.02, minimumBalance
= 100) {
super(initialDeposit, accountHolder);
this.#interestRate = interestRate;
this.#minimumBalance = minimumBalance;
}
// Override parent method
withdraw(amount) {
const potentialBalance = this.balance - amount;
if (potentialBalance < this.#minimumBalance) {
throw new Error(`Withdrawal would bring balance below minimum of $
{this.#minimumBalance}`);
}
return super.withdraw(amount);
}
calculateInterest() {
const interest = this.balance * this.#interestRate;
this.deposit(interest);
return interest;
}
get interestRate() {
return this.#interestRate;
}
set interestRate(rate) {
if (rate < 0 || rate > 1) {
throw new Error('Interest rate must be between 0 and 1');
}
this.#interestRate = rate;
}
}
// 3. Mixin pattern for multiple inheritance
const Timestamped = (Base) => class extends Base {
constructor(...args) {
super(...args);
this.createdAt = new Date();
this.updatedAt = new Date();
}
touch() {
this.updatedAt = new Date();
}
getAge() {
return Date.now() - this.createdAt.getTime();
}
};
const Serializable = (Base) => class extends Base {
toJSON() {
const obj = {};
for (const key in this) {
if (this.hasOwnProperty(key) && !key.startsWith('_')) {
obj[key] = this[key];
}
}
return obj;
}
static fromJSON(json) {
const instance = Object.create(this.prototype);
Object.assign(instance, json);
return instance;
}
};
// 4. Applying mixins
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
getInfo() {
return `${this.name}: ${this.price}`;
}
}
// Create enhanced class with mixins
class EnhancedProduct extends Serializable(Timestamped(Product)) {
constructor(name, price, category) {
super(name, price);
this.category = category;
}
updatePrice(newPrice) {
this.price = newPrice;
this.touch(); // From Timestamped mixin
}
}
// 5. Abstract class pattern
class AbstractRepository {
constructor() {
if (this.constructor === AbstractRepository) {
throw new Error('Cannot instantiate abstract class');
}
}
// Abstract methods (must be implemented by subclasses)
async save(entity) {
throw new Error('save() method must be implemented');
}
async findById(id) {
throw new Error('findById() method must be implemented');
}
async findAll() {
throw new Error('findAll() method must be implemented');
}
// Concrete method (can be used by subclasses)
async exists(id) {
try {
const entity = await this.findById(id);
return entity !== null;
} catch (error) {
return false;
}
}
}
class UserRepository extends AbstractRepository {
constructor() {
super();
this.users = new Map();
this.nextId = 1;
}
async save(user) {
if (!user.id) {
user.id = this.nextId++;
}
this.users.set(user.id, {...user});
return user;
}
async findById(id) {
return this.users.get(id) || null;
}
async findAll() {
return Array.from(this.users.values());
}
async findByEmail(email) {
for (const user of this.users.values()) {
if (user.email === email) {
return user;
}
}
return null;
}
}
// 6. Factory pattern with classes
class AnimalFactory {
static createAnimal(type, name) {
switch (type.toLowerCase()) {
case 'dog':
return new Dog(name);
case 'cat':
return new Cat(name);
case 'bird':
return new Bird(name);
default:
throw new Error(`Unknown animal type: ${type}`);
}
}
}
class Animal {
constructor(name) {
this.name = name;
}
speak() {
throw new Error('speak() method must be implemented');
}
}
class Dog extends Animal {
speak() {
return `${this.name} says Woof!`;
}
}
class Cat extends Animal {
speak() {
return `${this.name} says Meow!`;
}
}
class Bird extends Animal {
speak() {
return `${this.name} says Tweet!`;
}
}
// 7. Singleton pattern
class DatabaseConnection {
static #instance = null;
#isConnected = false;
constructor() {
if (DatabaseConnection.#instance) {
return DatabaseConnection.#instance;
}
DatabaseConnection.#instance = this;
this.connectionId = Math.random().toString(36).substr(2, 9);
}
static getInstance() {
if (!DatabaseConnection.#instance) {
DatabaseConnection.#instance = new DatabaseConnection();
}
return DatabaseConnection.#instance;
}
connect() {
if (!this.#isConnected) {
console.log(`Connecting to database with ID: ${this.connectionId}`);
this.#isConnected = true;
}
return this;
}
disconnect() {
if (this.#isConnected) {
console.log('Disconnecting from database');
this.#isConnected = false;
}
return this;
}
get isConnected() {
return this.#isConnected;
}
}
// Usage examples
const account1 = new SavingsAccount(1000, 'John Doe', 0.05, 500);
const account2 = new BankAccount(500, 'Jane Smith');
console.log('Account balances:', BankAccount.compareBalances(account1, account2));
const product = new EnhancedProduct('Laptop', 999, 'Electronics');
console.log('Product age:', product.getAge(), 'ms');
console.log('Product JSON:', JSON.stringify(product));
const userRepo = new UserRepository();
const animals = [
AnimalFactory.createAnimal('dog', 'Buddy'),
AnimalFactory.createAnimal('cat', 'Whiskers')
];
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
console.log('Same instance:', db1 === db2); // true
16. Prototypical Inheritance
JavaScript's inheritance model based on prototypes rather than classes.
Critical Analysis
Arrays in JavaScript are actually objects with numeric keys
They provide constant-time access by index but variable-time insertion/deletion
Memory is allocated dynamically, making them flexible but potentially less memory-
efficient than fixed arrays
Step-by-Step Example
javascript// 1. Array creation
const fruits = ['apple', 'banana', 'orange'];
const numbers = new Array(1, 2, 3, 4, 5);
const mixed = [1, 'hello', true, null, {name: 'John'}];
// 2. Access elements
console.log(fruits[0]); // 'apple'
console.log(fruits.length); // 3
// 3. Modify arrays
fruits.push('grape'); // Add to end
fruits.unshift('mango'); // Add to beginning
const removed = fruits.pop(); // Remove from end
const first = fruits.shift(); // Remove from beginning
// 4. Array methods
const sliced = fruits.slice(1, 3); // Extract portion
const joined = fruits.join(', '); // Convert to string
const found = fruits.find(fruit => fruit.includes('a')); // Find element
2. Functions
Functions are reusable blocks of code that perform specific tasks. They're first-
class objects in JavaScript.
Critical Analysis
Functions create their own execution context and scope
They can be hoisted (function declarations) or not (function expressions)
JavaScript functions are closures by default, capturing their lexical environment
Step-by-Step Example
javascript// 1. Function declaration (hoisted)
function calculateArea(width, height) {
// Local scope
const area = width * height;
return area;
}
// 2. Function expression (not hoisted)
const calculateVolume = function(length, width, height) {
return length * width * height;
};
// 3. Function with default parameters
function greet(name = 'Guest', greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
// 4. Function with rest parameters
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
// Usage examples
console.log(calculateArea(5, 3)); // 15
console.log(greet()); // 'Hello, Guest!'
console.log(sum(1, 2, 3, 4, 5)); // 15
3. Arrow Functions
Arrow functions provide a concise syntax for writing functions and have lexical
this binding.
Critical Analysis
Arrow functions don't have their own this, arguments, super, or new.target
They cannot be used as constructors
Implicit return for single expressions makes code more concise
Best for callbacks and short functions, not methods
Step-by-Step Example
javascript// 1. Basic arrow function syntax
const add = (a, b) => a + b; // Implicit return
const multiply = (a, b) => {
// Explicit return needed with block
const result = a * b;
return result;
};
// 2. Single parameter (parentheses optional)
const square = x => x * x;
const double = (x) => x * 2;
// 3. No parameters
const getRandomNumber = () => Math.random();
// 4. Arrow functions in array methods
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
// 5. Lexical this binding
class Counter {
constructor() {
this.count = 0;
}
// Regular method
incrementRegular() {
setTimeout(function() {
this.count++; // 'this' is undefined or window
}, 1000);
}
// Arrow function preserves 'this'
incrementArrow() {
setTimeout(() => {
this.count++; // 'this' refers to Counter instance
}, 1000);
}
}
4. Advanced Arrays
Advanced array operations including higher-order functions and complex
manipulations.
Critical Analysis
Higher-order functions promote functional programming paradigms
Methods like map, filter, reduce are immutable (don't modify original array)
Chain operations can impact performance with large datasets
Understanding time complexity is crucial for optimization
Step-by-Step Example
javascriptconst students = [
{name: 'Alice', age: 20, grade: 85},
{name: 'Bob', age: 22, grade: 92},
{name: 'Charlie', age: 21, grade: 78},
{name: 'Diana', age: 23, grade: 96}
];
// 1. Map - transform each element
const names = students.map(student => student.name);
const grades = students.map(student => ({...student, letterGrade:
getLetterGrade(student.grade)}));
// 2. Filter - select elements based on condition
const highPerformers = students.filter(student => student.grade >= 90);
const adults = students.filter(student => student.age >= 21);
// 3. Find methods
const topStudent = students.find(student => student.grade > 95);
const bobIndex = students.findIndex(student => student.name === 'Bob');
// 4. Some and Every
const hasHighGrade = students.some(student => student.grade > 90); // true
const allPassed = students.every(student => student.grade >= 60); // true
// 5. Sort (mutates original array)
const sortedByGrade = [...students].sort((a, b) => b.grade - a.grade);
const sortedByName = [...students].sort((a, b) => a.name.localeCompare(b.name));
// 6. Flat and FlatMap
const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flattened = nestedArray.flat(); // [1, 2, 3, 4, 5, 6]
const sentences = ['Hello world', 'How are you'];
const words = sentences.flatMap(sentence => sentence.split(' '));
function getLetterGrade(score) {
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
if (score >= 60) return 'D';
return 'F';
}
5. Destructuring
Destructuring allows unpacking values from arrays or properties from objects into
distinct variables.
Critical Analysis
Provides cleaner, more readable code for extracting data
Supports default values and nested destructuring
Can impact performance slightly due to additional operations
Excellent for function parameters and return values
Step-by-Step Example
javascript// 1. Array destructuring
const colors = ['red', 'green', 'blue', 'yellow'];
const [primary, secondary, tertiary] = colors;
const [first, , third] = colors; // Skip elements
const [head, ...tail] = colors; // Rest pattern
// 2. Object destructuring
const person = {
name: 'John Doe',
age: 30,
address: {
street: '123 Main St',
city: 'New York',
country: 'USA'
},
hobbies: ['reading', 'gaming']
};
const {name, age, email = 'No email'} = person; // Default value
const {name: fullName, age: years} = person; // Rename variables
const {address: {city, country}} = person; // Nested destructuring
const {hobbies: [firstHobby]} = person; // Mixed destructuring
// 3. Function parameter destructuring
function createUser({name, age, email = 'not provided'}) {
return {
id: Math.random(),
name,
age,
email,
created: new Date()
};
}
function processCoordinates([x, y, z = 0]) {
return {x, y, z};
}
// 4. Swapping variables
let a = 1, b = 2;
[a, b] = [b, a]; // a = 2, b = 1
// 5. Multiple return values
function getMinMax(numbers) {
return [Math.min(...numbers), Math.max(...numbers)];
}
const [min, max] = getMinMax([1, 5, 3, 9, 2]);
6. Optional Chaining
Optional chaining (?.) allows safe access to nested object properties without
throwing errors.
Critical Analysis
Prevents common TypeError: Cannot read property of undefined
Makes code more defensive and robust
Can mask underlying data structure issues if overused
Supported in ES2020+, requires transpilation for older browsers
Step-by-Step Example
javascript// 1. Basic optional chaining
const user = {
id: 1,
profile: {
name: 'John',
address: {
street: '123 Main St'
}
}
};
// Without optional chaining (error-prone)
// const street = user.profile.address.street; // Could throw error
// With optional chaining (safe)
const street = user.profile?.address?.street; // '123 Main St'
const zipCode = user.profile?.address?.zip; // undefined (no error)
// 2. Optional chaining with arrays
const users = [
{name: 'John', posts: [{title: 'Hello'}]},
{name: 'Jane'} // No posts property
];
const firstPostTitle = users[0]?.posts?.[0]?.title; // 'Hello'
const secondUserFirstPost = users[1]?.posts?.[0]?.title; // undefined
// 3. Optional chaining with methods
const api = {
getData: function() {
return {items: [1, 2, 3]};
}
};
const data = api.getData?.(); // Calls method if it exists
const items = api.getUsers?.()?.items; // undefined (method doesn't exist)
// 4. Dynamic property access
const property = 'address';
const dynamicAccess = user.profile?.[property]?.street;
// 5. Practical example with API response
function displayUserInfo(apiResponse) {
const userName = apiResponse.data?.user?.profile?.displayName ?? 'Anonymous';
const avatarUrl = apiResponse.data?.user?.profile?.avatar?.url ?? '/default-
avatar.png';
const lastLogin = apiResponse.data?.user?.metadata?.lastLoginDate;
return {
name: userName,
avatar: avatarUrl,
lastSeen: lastLogin ? new Date(lastLogin).toLocaleDateString() : 'Never'
};
}
7. Nullish Coalescing
The nullish coalescing operator (??) provides a way to handle null/undefined values
specifically.
Critical Analysis
More precise than || operator for default values
Only considers null and undefined as "nullish"
Prevents issues with falsy values like 0, '', false
Works well with optional chaining for robust data handling
Step-by-Step Example
javascript// 1. Basic nullish coalescing
const userInput = null;
const defaultValue = 'Default text';
// Using || (problematic with falsy values)
const result1 = userInput || defaultValue; // 'Default text'
const result2 = 0 || defaultValue; // 'Default text' (unintended)
const result3 = '' || defaultValue; // 'Default text' (unintended)
// Using ?? (only null/undefined trigger default)
const result4 = userInput ?? defaultValue; // 'Default text'
const result5 = 0 ?? defaultValue; // 0 (intended)
const result6 = '' ?? defaultValue; // '' (intended)
// 2. Configuration objects
function createConfig(options = {}) {
return {
theme: options.theme ?? 'light',
timeout: options.timeout ?? 5000,
debug: options.debug ?? false,
maxRetries: options.maxRetries ?? 3
};
}
// This works correctly even with falsy but valid values
const config1 = createConfig({theme: 'dark', timeout: 0, debug: false});
// Result: {theme: 'dark', timeout: 0, debug: false, maxRetries: 3}
// 3. API data processing
function processApiData(response) {
return {
title: response.data?.title ?? 'Untitled',
description: response.data?.description ?? 'No description available',
count: response.data?.count ?? 0, // 0 is valid, null/undefined are not
isActive: response.data?.isActive ?? true,
tags: response.data?.tags ?? []
};
}
// 4. Chaining with optional chaining
const user = {
preferences: {
notifications: null,
theme: undefined
}
};
const notifications = user.preferences?.notifications ?? 'enabled';
const theme = user.preferences?.theme ?? 'system';
// 5. Nullish coalescing assignment (ES2021)
let settings = {timeout: null};
settings.timeout ??= 5000; // Only assigns if null or undefined
// settings.timeout is now 5000
let counter = 0;
counter ??= 10; // counter remains 0 (not null or undefined)
8. Objects
Objects are collections of key-value pairs and the foundation of JavaScript's data
structures.
Critical Analysis
Objects are reference types, not primitive values
Property access can be dynamic using bracket notation
Object methods can lose their context when passed as callbacks
Understanding prototypal inheritance is crucial for object behavior
Step-by-Step Example
javascript// 1. Object creation methods
const person1 = {
name: 'John',
age: 30,
greet: function() {
return `Hello, I'm ${this.name}`;
}
};
const person2 = new Object();
person2.name = 'Jane';
person2.age = 25;
const person3 = Object.create(null); // No prototype
person3.name = 'Bob';
// 2. Property access
console.log(person1.name); // Dot notation
console.log(person1['age']); // Bracket notation
const prop = 'name';
console.log(person1[prop]); // Dynamic access
// 3. Object methods and 'this'
const calculator = {
value: 0,
add(num) {
this.value += num;
return this; // Method chaining
},
multiply(num) {
this.value *= num;
return this;
},
getValue() {
return this.value;
}
};
const result = calculator.add(5).multiply(3).getValue(); // 15
// 4. Object manipulation
const original = {a: 1, b: 2};
const copy = {...original}; // Shallow copy
const merged = {...original, c: 3, b: 4}; // Merge and override
// Object.assign
const target = {a: 1};
const source = {b: 2, c: 3};
Object.assign(target, source); // Mutates target
// 5. Object utility methods
const obj = {name: 'John', age: 30, city: 'NYC'};
const keys = Object.keys(obj); // ['name', 'age', 'city']
const values = Object.values(obj); // ['John', 30, 'NYC']
const entries = Object.entries(obj); // [['name', 'John'], ['age', 30], ['city',
'NYC']]
// Convert back from entries
const reconstructed = Object.fromEntries(entries);
// 6. Property descriptors
Object.defineProperty(obj, 'id', {
value: 123,
writable: false,
enumerable: false,
configurable: false
});
// 7. Getters and setters
const user = {
firstName: 'John',
lastName: 'Doe',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(' ');
}
};
console.log(user.fullName); // 'John Doe'
user.fullName = 'Jane Smith';
console.log(user.firstName); // 'Jane'
9. Arrays of Objects
Working with collections of objects is common in JavaScript applications.
Critical Analysis
Combines array iteration methods with object property access
Performance considerations when dealing with large datasets
Sorting and filtering strategies impact memory usage
Normalization vs. nested structures trade-offs
Step-by-Step Example
javascript// 1. Sample data structure
const employees = [
{id: 1, name: 'Alice Johnson', department: 'Engineering', salary: 85000,
skills: ['JavaScript', 'React']},
{id: 2, name: 'Bob Smith', department: 'Marketing', salary: 65000, skills:
['SEO', 'Analytics']},
{id: 3, name: 'Carol Williams', department: 'Engineering', salary: 92000,
skills: ['Python', 'Django']},
{id: 4, name: 'David Brown', department: 'Sales', salary: 58000, skills:
['Communication', 'CRM']}
];
// 2. Filtering operations
const engineers = employees.filter(emp => emp.department === 'Engineering');
const highEarners = employees.filter(emp => emp.salary > 70000);
const javascriptDevs = employees.filter(emp =>
emp.skills.includes('JavaScript')
);
// 3. Transformation operations
const employeeNames = employees.map(emp => emp.name);
const salaryInfo = employees.map(emp => ({
name: emp.name,
salary: emp.salary,
salaryRange: getSalaryRange(emp.salary)
}));
// 4. Aggregation operations
const totalSalaries = employees.reduce((sum, emp) => sum + emp.salary, 0);
const averageSalary = totalSalaries / employees.length;
const departmentCounts = employees.reduce((acc, emp) => {
acc[emp.department] = (acc[emp.department] || 0) + 1;
return acc;
}, {});
// 5. Sorting operations
const sortedBySalary = [...employees].sort((a, b) => b.salary - a.salary);
const sortedByName = [...employees].sort((a, b) => a.name.localeCompare(b.name));
// 6. Complex queries
const engineersWithJS = employees
.filter(emp => emp.department === 'Engineering')
.filter(emp => emp.skills.includes('JavaScript'))
.map(emp => ({name: emp.name, salary: emp.salary}))
.sort((a, b) => b.salary - a.salary);
// 7. Grouping operations
const groupByDepartment = employees.reduce((groups, emp) => {
const dept = emp.department;
if (!groups[dept]) {
groups[dept] = [];
}
groups[dept].push(emp);
return groups;
}, {});
// 8. Finding operations
const highestPaid = employees.reduce((max, emp) =>
emp.salary > max.salary ? emp : max
);
const employeeById = (id) => employees.find(emp => emp.id === id);
// 9. Update operations (immutable)
const giveRaise = (employeeId, raiseAmount) => {
return employees.map(emp =>
emp.id === employeeId
? {...emp, salary: emp.salary + raiseAmount}
: emp
);
};
function getSalaryRange(salary) {
if (salary < 50000) return 'Low';
if (salary < 80000) return 'Medium';
return 'High';
}
10. Advanced Control Flow
Sophisticated control structures including switch statements, ternary operators,
and short-circuit evaluation.
Critical Analysis
Choose the right control structure for readability and performance
Switch statements with fall-through can be error-prone
Ternary operators should be used judiciously for readability
Short-circuit evaluation can optimize performance and code brevity
Step-by-Step Example
javascript// 1. Enhanced switch statements
function getSeasonInfo(month) {
switch (month.toLowerCase()) {
case 'december':
case 'january':
case 'february':
return {season: 'Winter', temperature: 'Cold', activities: ['Skiing',
'Hot cocoa']};
case 'march':
case 'april':
case 'may':
return {season: 'Spring', temperature: 'Mild', activities:
['Gardening', 'Hiking']};
case 'june':
case 'july':
case 'august':
return {season: 'Summer', temperature: 'Hot', activities: ['Swimming',
'Beach']};
case 'september':
case 'october':
case 'november':
return {season: 'Fall', temperature: 'Cool', activities: ['Leaf
peeping', 'Apple picking']};
default:
throw new Error(`Invalid month: ${month}`);
}
}
// 2. Complex ternary operations
const user = {role: 'admin', isActive: true, permissions: ['read', 'write',
'delete']};
const accessLevel = user.role === 'admin'
? user.isActive
? 'full-access'
: 'restricted-admin'
: user.role === 'user'
? 'standard-access'
: 'no-access';
// 3. Short-circuit evaluation patterns
const config = {
theme: userPreferences.theme || 'light',
timeout: userSettings.timeout || defaultTimeout,
features: userAccount.isPremium && premiumFeatures
};
// Conditional execution
user.isLoggedIn && updateLastActive();
!user.hasSeenWelcome && showWelcomeMessage();
// 4. Guard clauses and early returns
function processOrder(order) {
// Guard clauses
if (!order) {
throw new Error('Order is required');
}
if (!order.items || order.items.length === 0) {
throw new Error('Order must have items');
}
if (order.total < 0) {
throw new Error('Order total cannot be negative');
}
// Main logic after validation
const processedOrder = {
...order,
id: generateOrderId(),
timestamp: new Date(),
status: 'processing'
};
return processedOrder;
}
// 5. Complex conditional logic
function determineShippingCost(order) {
const {weight, destination, expedited, membershipLevel} = order;
// Base cost calculation
let cost = weight * 0.5;
// Destination modifier
if (destination === 'international') {
cost *= 3;
} else if (destination === 'remote') {
cost *= 1.5;
}
// Expedited shipping
if (expedited) {
cost += cost * 0.75;
}
// Membership discount
switch (membershipLevel) {
case 'premium':
cost *= 0.8; // 20% discount
break;
case 'gold':
cost *= 0.9; // 10% discount
break;
case 'silver':
cost *= 0.95; // 5% discount
break;
// No discount for basic or no membership
}
// Free shipping threshold
if (order.subtotal > 100) {
cost = Math.max(0, cost - 10);
}
return Math.round(cost * 100) / 100; // Round to 2 decimal places
}
// 6. State machine pattern
function createStateMachine(initialState) {
let currentState = initialState;
const transitions = {
idle: {
start: 'running',
reset: 'idle'
},
running: {
pause: 'paused',
stop: 'idle',
complete: 'completed'
},
paused: {
resume: 'running',
stop: 'idle'
},
completed: {
reset: 'idle'
}
};
return {
getCurrentState: () => currentState,
transition(action) {
const validTransitions = transitions[currentState];
if (validTransitions && validTransitions[action]) {
currentState = validTransitions[action];
return true;
}
return false; // Invalid transition
},
canTransition(action) {
return !!(transitions[currentState] && transitions[currentState]
[action]);
}
};
}
function generateOrderId() {
return 'ORD-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
}
11. Reduce
The reduce method is a powerful array function for aggregating data into a single
value.
Critical Analysis
Most versatile array method - can implement map, filter, and more
Has a steeper learning curve but offers maximum flexibility
Performance considerations with large datasets
Initial value parameter is crucial for correct behavior
Step-by-Step Example
javascript// 1. Basic reduce operations
const numbers = [1, 2, 3, 4, 5];
// Sum
const sum = numbers.reduce((accumulator, current) => accumulator + current, 0);
// Product
const product = numbers.reduce((acc, curr) => acc * curr, 1);
// Maximum value
const max = numbers.reduce((acc, curr) => Math.max(acc, curr), -Infinity);
// 2. Object aggregation
const transactions = [
{id: 1, amount: 100, type: 'credit', category: 'salary'},
{id: 2, amount: 50, type: 'debit', category: 'groceries'},
{id: 3, amount: 200, type: 'credit', category: 'freelance'},
{id: 4, amount: 30, type: 'debit', category: 'transport'},
{id: 5, amount: 75, type: 'debit', category: 'groceries'}
];
// Calculate balance
const balance = transactions.reduce((acc, transaction) => {
return transaction.type === 'credit'
? acc + transaction.amount
: acc - transaction.amount;
}, 0);
// Group by category
const byCategory = transactions.reduce((acc, transaction) => {
const category = transaction.category;
if (!acc[category]) {
acc[category] = {
totalAmount: 0,
count: 0,
transactions: []
};
}
acc[category].totalAmount += transaction.amount;
acc[category].count += 1;
acc[category].transactions.push(transaction);
return acc;
}, {});
// 3. Complex data transformations
const students = [
{name: 'Alice', grades: [85, 90, 78, 92]},
{name: 'Bob', grades: [76, 88, 85, 79]},
{name: 'Charlie', grades: [92, 95, 89, 94]}
];
// Calculate class statistics
const classStats = students.reduce((stats, student) => {
const average = student.grades.reduce((sum, grade) => sum + grade, 0) /
student.grades.length;
stats.students.push({
name: student.name,
average: average,
totalGrades: student.grades.length
});
stats.overallAverage += average;
stats.totalStudents += 1;
stats.highestAverage = Math.max(stats.highestAverage, average);
stats.lowestAverage = Math.min(stats.lowestAverage, average);
return stats;
}, {
students: [],
overallAverage: 0,
totalStudents: 0,
highestAverage: -Infinity,
lowestAverage: Infinity
});
// Finalize overall average
classStats.overallAverage /= classStats.totalStudents;
// 4. Implementing other array methods with reduce
const array = [1, 2, 3, 4, 5];
// Map implementation
const doubled = array.reduce((acc, curr) => {
acc.push(curr * 2);
return acc;
}, []);
// Filter implementation
const evens = array.reduce((acc, curr) => {
if (curr % 2 === 0) {
acc.push(curr);
}
return acc;
}, []);
// 5. Flatten nested arrays
const nestedArray = [[1, 2], [3, 4], [5, 6, 7]];
const flattened = nestedArray.reduce((acc, curr) => acc.concat(curr), []);
// 6. Count occurrences
const words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'];
const wordCount = words.reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1;
return acc;
}, {});
// 7. Pipeline operations
const pipeline = [
(data) => data.filter(x => x > 0),
(data) => data.map(x => x * 2),
(data) => data.reduce((sum, x) => sum + x, 0)
];
const data = [-1, 2, -3, 4, 5];
const result = pipeline.reduce((acc, fn) => fn(acc), data);
// Result: 22 (filtered: [2, 4, 5], doubled: [4, 8, 10], sum: 22)
// 8. Building complex objects
const apiData = [
{userId: 1, action: 'login', timestamp: '2023-01-01T10:00:00Z'},
{userId: 2, action: 'purchase', timestamp: '2023-01-01T11:00:00Z'},
{userId: 1, action: 'logout', timestamp: '2023-01-01T12:00:00Z'},
{userId: 1, action: 'login', timestamp: '2023-01-02T09:00:00Z'}
];
const userActivity = apiData.reduce((acc, log) => {
const userId = log.userId;
if (!acc[userId]) {
acc[userId] = {
totalActions: 0,
actions: {},
firstSeen: log.timestamp,
lastSeen: log.timestamp
};
}
const user = acc[userId];
user.totalActions++;
user.actions[log.action] = (user.actions[log.action] || 0) + 1;
user.lastSeen = log.timestamp > user.lastSeen ? log.timestamp : user.lastSeen;
user.firstSeen = log.timestamp < user.firstSeen ? log.timestamp :
user.firstSeen;
return acc;
}, {});
v2∙LatestCopyPublishJavaScript Explained Simply: Complete Beginner's Guide
1. Classes - Building Blueprints for Objects
What are Classes? (Simple Explanation)
Think of a class like a blueprint for building houses. The blueprint isn't a house
itself, but it shows you exactly how to build one. Every house built from that
blueprint will have the same basic structure, but each can have different colors,
furniture, etc.
Why Do We Need Classes?
Imagine you're making a video game with 100 different cars. Without classes, you'd
have to write the same code 100 times! Classes let you write the code once and
reuse it.
Step-by-Step Example (Like Teaching a 10-Year-Old)
javascript// Step 1: Create a blueprint (class) for making dogs
class Dog {
// Step 2: The constructor is like the "birth certificate" - it sets up each
new dog
constructor(name, breed, age) {
this.name = name; // "this" means "this specific dog"
this.breed = breed;
this.age = age;
this.energy = 100; // All dogs start with full energy
}
// Step 3: Add things dogs can do (methods)
bark() {
console.log(`${this.name} says: Woof! Woof!`);
this.energy -= 5; // Barking uses energy
}
play() {
if (this.energy > 20) {
console.log(`${this.name} is playing happily!`);
this.energy -= 20;
} else {
console.log(`${this.name} is too tired to play`);
}
}
sleep() {
console.log(`${this.name} is sleeping...`);
this.energy = 100; // Sleep restores energy
}
// Step 4: Add information we can ask for (getter)
get description() {
return `${this.name} is a ${this.age} year old ${this.breed}`;
}
}
// Step 5: Use the blueprint to create actual dogs
const myDog = new Dog("Buddy", "Golden Retriever", 3);
const friendDog = new Dog("Max", "German Shepherd", 5);
// Step 6: Make the dogs do things
myDog.bark(); // "Buddy says: Woof! Woof!"
myDog.play(); // "Buddy is playing happily!"
console.log(myDog.description); // "Buddy is a 3 year old Golden Retriever"
Why This is Better Than Old Ways
Before classes, we had to write messy code that was hard to understand. Classes
organize everything neatly, like putting all dog-related things in a "Dog folder"
on your computer.
2. Callbacks - Functions That Call Back Later
What are Callbacks? (Simple Explanation)
A callback is like ordering pizza and giving them your phone number. You don't wait
by the phone - you do other things. When the pizza is ready, they "call you back"
using the number you gave them.
Why Do We Need Callbacks?
Some things take time (like downloading a file or asking a server for data).
Instead of waiting and doing nothing, we can do other things and let JavaScript
"call us back" when it's done.
Step-by-Step Example (Like Teaching a 10-Year-Old)
javascript// Step 1: Imagine you're a teacher giving homework to students
function giveHomework(studentName, whenFinished) {
console.log(`${studentName}, please do your math homework`);
// Homework takes time (we'll pretend it takes 2 seconds)
setTimeout(function() {
console.log(`${studentName} finished the homework!`);
// Step 2: When homework is done, we "call back" to report it
whenFinished(studentName + "'s homework is complete!");
}, 2000);
}
// Step 3: Create a function that handles what happens when homework is done
function homeworkComplete(message) {
console.log("Teacher received: " + message);
console.log("Good job! Here's a gold star! ⭐");
}
// Step 4: Give homework and tell what to do when it's finished
giveHomework("Alice", homeworkComplete);
// Step 5: The program continues running other code while waiting
console.log("Teacher is now helping other students...");
// Output order will be:
// "Alice, please do your math homework"
// "Teacher is now helping other students..."
// (2 seconds later...)
// "Alice finished the homework!"
// "Teacher received: Alice's homework is complete!"
// "Good job! Here's a gold star! ⭐"
Real-Life Example - Ordering Food
javascriptfunction orderFood(foodItem, whenReady) {
console.log(`Ordering ${foodItem}...`);
// Different foods take different times to prepare
const cookingTime = foodItem === "pizza" ? 3000 : 1000;
setTimeout(function() {
console.log(`${foodItem} is ready!`);
whenReady(foodItem);
}, cookingTime);
}
function eatFood(food) {
console.log(`Yum! Eating delicious ${food}! 😋`);
}
// Order multiple items - they'll be ready at different times
orderFood("sandwich", eatFood);
orderFood("pizza", eatFood);
console.log("While waiting, I'll watch TV...");
The Problem with Callbacks (Callback Hell)
javascript// This gets messy when you have many steps:
getUser(userId, function(user) {
getUser Posts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
getAuthor(comments[0].authorId, function(author) {
// This pyramid shape is "callback hell" - hard to read!
console.log(author.name);
});
});
});
});
3. Asynchronous Logic - Doing Multiple Things at Once
What is Asynchronous? (Simple Explanation)
Synchronous is like washing dishes one by one - you finish one completely before
starting the next.
Asynchronous is like doing laundry - you start the washing machine, then while it's
running, you can cook dinner, check email, etc. You don't just stand there waiting!
Why is This Important?
Websites need to download images, get data from servers, and respond to user clicks
all at the same time. If everything happened one-by-one, websites would be
incredibly slow!
Step-by-Step Example (Like Teaching a 10-Year-Old)
javascript// Step 1: Let's simulate different tasks that take different amounts of
time
function brushTeeth() {
console.log("🦷 Started brushing teeth...");
setTimeout(() => {
console.log("🦷 Finished brushing teeth!");
}, 2000); // Takes 2 seconds
}
function makeBreakfast() {
console.log("🍳 Started making breakfast...");
setTimeout(() => {
console.log("🍳 Breakfast is ready!");
}, 3000); // Takes 3 seconds
}
function checkWeather() {
console.log(" Checking weather...");
setTimeout(() => {
console.log(" It's sunny today!");
}, 1000); // Takes 1 second
}
// Step 2: Synchronous way (slow and boring)
console.log("=== SYNCHRONOUS WAY (Slow) ===");
console.log("Wake up!");
// If we did everything one by one, it would take 6 seconds total
// But we can't show this easily with setTimeout, so let's show the async way:
// Step 3: Asynchronous way (fast and efficient!)
console.log("=== ASYNCHRONOUS WAY (Fast!) ===");
console.log("Wake up!");
// Start all tasks at the same time!
brushTeeth(); // Starts immediately
makeBreakfast(); // Also starts immediately
checkWeather(); // Also starts immediately
console.log("While everything is happening, I can get dressed!");
// All tasks finish when they're ready, not in any particular order
Understanding the Event Loop (The Brain Behind Asynchronous)
javascript// The Event Loop is like a very organized assistant who manages your
tasks
console.log("1: I'm first!"); // Happens immediately
setTimeout(() => {
console.log("2: I'm from setTimeout!"); // Goes to the "slow tasks" line
}, 0); // Even with 0 delay, it still goes to the back of the line!
Promise.resolve().then(() => {
console.log("3: I'm from Promise!"); // Promises get priority over setTimeout
});
console.log("4: I'm also immediate!"); // Happens immediately
// Output order: 1, 4, 3, 2
// Why? Because immediate code runs first, then Promises, then setTimeout
Real-World Example - Making Multiple API Calls
javascript// Imagine we're building a social media app and need to get:
// 1. User info
// 2. User's posts
// 3. User's friends
function getUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ name: "Alice", id: 1 });
}, 1000);
});
}
function getPosts() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1500);
});
}
function getFriends() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(["Bob", "Charlie", "Diana"]);
}, 800);
});
}
// Sequential way (slow - takes 3.3 seconds total)
async function loadDataSlowly() {
console.log("Loading user data slowly...");
const user = await getUser(); // Wait 1 second
const posts = await getPosts(); // Wait another 1.5 seconds
const friends = await getFriends(); // Wait another 0.8 seconds
console.log("Done! Took 3.3 seconds total");
}
// Parallel way (fast - takes 1.5 seconds total)
async function loadDataFast() {
console.log("Loading user data quickly...");
// Start all requests at the same time!
const [user, posts, friends] = await Promise.all([
getUser(), // Takes 1 second
getPosts(), // Takes 1.5 seconds
getFriends() // Takes 0.8 seconds
]);
// We only wait for the slowest one (1.5 seconds)
console.log("Done! Took only 1.5 seconds total");
}
4. Advanced Classes - Making Classes Even More Powerful
What Makes Classes "Advanced"?
Basic classes are like basic LEGO sets. Advanced classes are like LEGO Technic -
they have more sophisticated features like inheritance (building on top of other
classes), private parts (secret compartments), and static methods (tools that
belong to the factory, not individual toys).
Inheritance - Building Upon Other Classes
javascript// Step 1: Create a basic Animal class (like a general blueprint)
class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
this.energy = 100;
}
eat(food) {
console.log(`${this.name} is eating ${food}`);
this.energy += 20;
}
sleep() {
console.log(`${this.name} is sleeping`);
this.energy = 100;
}
makeSound() {
console.log(`${this.name} makes a sound`);
}
}
// Step 2: Create a more specific Dog class that inherits from Animal
class Dog extends Animal {
constructor(name, breed) {
// Step 3: Use 'super' to call the parent constructor
super(name, "Canine"); // This calls Animal's constructor
this.breed = breed;
this.tricks = [];
}
// Step 4: Override parent methods (replace them with dog-specific versions)
makeSound() {
console.log(`${this.name} barks: Woof! Woof!`);
}
// Step 5: Add dog-specific methods
learnTrick(trick) {
this.tricks.push(trick);
console.log(`${this.name} learned to ${trick}!`);
}
performTrick() {
if (this.tricks.length > 0) {
const randomTrick = this.tricks[Math.floor(Math.random() *
this.tricks.length)];
console.log(`${this.name} performs: ${randomTrick}!`);
} else {
console.log(`${this.name} doesn't know any tricks yet`);
}
}
}
// Step 6: Create an even more specific class
class ServiceDog extends Dog {
constructor(name, breed, serviceType) {
super(name, breed);
this.serviceType = serviceType;
this.isWorking = false;
}
startWorking() {
this.isWorking = true;
console.log(`${this.name} is now working as a ${this.serviceType} dog`);
}
stopWorking() {
this.isWorking = false;
console.log(`${this.name} is off duty and can play now!`);
}
}
// Using our inheritance hierarchy
const buddy = new Dog("Buddy", "Golden Retriever");
buddy.eat("kibble"); // From Animal class
buddy.makeSound(); // From Dog class (overridden)
buddy.learnTrick("sit"); // From Dog class
buddy.performTrick(); // From Dog class
const guide = new ServiceDog("Rex", "German Shepherd", "guide");
guide.startWorking(); // From ServiceDog class
guide.eat("dog food"); // From Animal class (inherited through Dog)
Private Fields - Secret Compartments
javascript// Private fields are like having a diary with a lock - only you can
access it
class BankAccount {
// Step 1: Private fields start with #
#balance = 0; // Secret! Can't be accessed from outside
#accountNumber; // Also secret!
#pin; // Super secret!
constructor(initialBalance, pin) {
this.#balance = initialBalance;
this.#accountNumber = this.#generateAccountNumber();
this.#pin = pin;
this.owner = ""; // This is public - anyone can see it
}
// Step 2: Private methods also start with #
#generateAccountNumber() {
return "ACC" + Math.random().toString().substr(2, 8);
}
#validatePin(enteredPin) {
return enteredPin === this.#pin;
}
// Step 3: Public methods can access private fields
deposit(amount, pin) {
if (!this.#validatePin(pin)) {
console.log("❌ Wrong PIN! Access denied!");
return false;
}
if (amount > 0) {
this.#balance += amount;
console.log(`✅ Deposited $${amount}. New balance: $${this.#balance}`);
return true;
} else {
console.log("❌ Can't deposit negative amount!");
return false;
}
}
withdraw(amount, pin) {
if (!this.#validatePin(pin)) {
console.log("❌ Wrong PIN! Access denied!");
return false;
}
if (amount > this.#balance) {
console.log("❌ Insufficient funds!");
return false;
}
this.#balance -= amount;
console.log(`✅ Withdrew $${amount}. New balance: $${this.#balance}`);
return true;
}
// Step 4: Getter to safely access private data
get balance() {
return `$${this.#balance}`;
}
get accountInfo() {
return `Account: ${this.#accountNumber}, Owner: ${this.owner}`;
}
}
// Using private fields
const myAccount = new BankAccount(1000, "1234");
myAccount.owner = "Alice";
console.log(myAccount.accountInfo); // ✅ This works
console.log(myAccount.balance); // ✅ This works (uses getter)
// These won't work - they're private!
// console.log(myAccount.#balance); // ❌ Error!
// console.log(myAccount.#pin); // ❌ Error!
myAccount.deposit(500, "1234"); // ✅ Correct PIN
myAccount.withdraw(200, "0000"); // ❌ Wrong PIN
Static Methods - Factory Tools
javascript// Static methods belong to the class itself, not to individual objects
// Think of them as tools in the factory, not features of the products
class MathHelper {
// Step 1: Static methods use the 'static' keyword
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static isEven(number) {
return number % 2 === 0;
}
// Step 2: Static properties
static PI = 3.14159;
static version = "1.0";
}
// Step 3: Call static methods on the class itself, not on instances
console.log(MathHelper.add(5, 3)); // 8
console.log(MathHelper.multiply(4, 7)); // 28
console.log(MathHelper.isEven(10)); // true
console.log(MathHelper.PI); // 3.14159
// You DON'T create an instance for static methods
// const helper = new MathHelper(); // Not needed for static methods!
// Real-world example: User management
class User {
static #allUsers = []; // Private static field
static #nextId = 1;
constructor(name, email) {
this.id = User.#nextId++;
this.name = name;
this.email = email;
this.createdAt = new Date();
// Add to the class-level user list
User.#allUsers.push(this);
}
// Static method to find users
static findById(id) {
return User.#allUsers.find(user => user.id === id);
}
static findByEmail(email) {
return User.#allUsers.find(user => user.email === email);
}
static getAllUsers() {
return [...User.#allUsers]; // Return a copy for safety
}
static getUserCount() {
return User.#allUsers.length;
}
}
// Using static methods for user management
const alice = new User("Alice", "
[email protected]");
const bob = new User("Bob", "
[email protected]");
const charlie = new User("Charlie", "
[email protected]");
console.log(User.getUserCount()); // 3
console.log(User.findById(2)); // Bob's user object
console.log(User.findByEmail("[email protected]")); // Alice's user object
5. Prototypical Inheritance - How JavaScript Really Works Under the Hood
What is Prototypical Inheritance? (Simple Explanation)
Imagine you have a magic notebook. When you can't find something in your notebook,
it automatically checks your teacher's notebook. If it's not there, it checks the
principal's notebook. This chain of checking is like JavaScript's prototype chain!
Why Do We Need to Understand This?
Classes in JavaScript are just fancy wrappers around prototypes. Understanding
prototypes helps you understand what's really happening and debug weird issues.
Step-by-Step Example (Like Teaching a 10-Year-Old)
javascript// Step 1: Every object in JavaScript has a hidden link to another object
(its prototype)
// Let's create a simple object
const animal = {
species: "Unknown",
makeSound() {
console.log("Some generic animal sound");
},
sleep() {
console.log(`The ${this.species} is sleeping`);
}
};
// Step 2: Create another object that "inherits" from animal
const dog = {
species: "Dog",
breed: "Unknown"
};
// Step 3: Set up the prototype chain (the magic link!)
Object.setPrototypeOf(dog, animal);
// Now dog can use animal's methods even though it doesn't have them!
dog.makeSound(); // "Some generic animal sound"
dog.sleep(); // "The Dog is sleeping"
// Step 4: Dog can override methods
dog.makeSound = function() {
console.log("Woof! Woof!");
};
dog.makeSound(); // Now it says "Woof! Woof!" instead
The Old Way (Before Classes) - Constructor Functions
javascript// Step 1: Create a constructor function (like a class blueprint)
function Animal(species) {
this.species = species;
this.energy = 100;
}
// Step 2: Add methods to the prototype (shared by all instances)
Animal.prototype.eat = function(food) {
console.log(`${this.species} is eating ${food}`);
this.energy += 20;
};
Animal.prototype.sleep = function() {
console.log(`${this.species} is sleeping`);
this.energy = 100;
};
// Step 3: Create a more specific constructor
function Dog(name, breed) {
// Call the parent constructor
Animal.call(this, "Dog"); // Like calling super() in classes
this.name = name;
this.breed = breed;
}
// Step 4: Set up inheritance (this is the tricky part!)
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Step 5: Add dog-specific methods
Dog.prototype.bark = function() {
console.log(`${this.name} says: Woof!`);
};
// Override parent method
Dog.prototype.eat = function(food) {
console.log(`${this.name} the ${this.breed} is eating ${food}`);
this.energy += 20;
};
// Step 6: Create and use objects
const buddy = new Dog("Buddy", "Golden Retriever");
buddy.eat("kibble"); // Uses Dog's version
buddy.sleep(); // Uses Animal's version
buddy.bark(); // Uses Dog's specific method
Understanding the Prototype Chain Visually
javascript// Let's trace what happens when we call buddy.sleep()
const buddy = new Dog("Buddy", "Golden Retriever");
// When we call buddy.sleep(), JavaScript looks for 'sleep' in this order:
// 1. First, check buddy object itself
console.log(buddy.hasOwnProperty('sleep')); // false - not found here
// 2. Next, check Dog.prototype
console.log(Dog.prototype.hasOwnProperty('sleep')); // false - not found here
// 3. Next, check Animal.prototype
console.log(Animal.prototype.hasOwnProperty('sleep')); // true - found it!
// 4. If not found, check Object.prototype (the root of all objects)
// 5. If still not found, return undefined
// We can see the prototype chain:
console.log("buddy's prototype chain:");
console.log("buddy ->", Object.getPrototypeOf(buddy) === Dog.prototype);
console.log("Dog.prototype ->", Object.getPrototypeOf(Dog.prototype) ===
Animal.prototype);
console.log("Animal.prototype ->", Object.getPrototypeOf(Animal.prototype) ===
Object.prototype);
console.log("Object.prototype ->", Object.getPrototypeOf(Object.prototype) ===
null);
Modern Approach with Object.create()
javascript// Step 1: Create a base object with methods
const vehiclePrototype = {
init(brand, model) {
this.brand = brand;
this.model = model;
this.speed = 0;
return this; // Return this for chaining
},
accelerate(amount) {
this.speed += amount;
console.log(`${this.brand} ${this.model} is now going ${this.speed} mph`);
return this;
},
brake() {
this.speed = Math.max(0, this.speed - 10);
console.log(`${this.brand} ${this.model} slowed down to ${this.speed}
mph`);
return this;
}
};
// Step 2: Create a more specific prototype
const carPrototype = Object.create(vehiclePrototype);
carPrototype.honk = function() {
console.log(`${this.brand} ${this.model}: BEEP BEEP!`);
return this;
};
carPrototype.openTrunk = function() {
console.log(`Opening ${this.brand} ${this.model}'s trunk`);
return this;
};
// Step 3: Factory function to create cars
function createCar(brand, model) {
return Object.create(carPrototype).init(brand, model);
}
// Step 4: Use it!
const myCar = createCar("Toyota", "Camry");
myCar.accelerate(30).honk().brake().openTrunk();
// This approach is more flexible than classes in some ways!
Why Understanding Prototypes Matters
javascript// Understanding prototypes helps you debug weird issues like this:
Array.prototype.last = function() {
return this[this.length - 1];
};
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.last()); // 5
// Now ALL arrays have this method!
const colors = ["red", "green", "blue"];
console.log(colors.last()); // "blue"
// This is how libraries like jQuery add methods to existing objects
// But be careful - modifying built-in prototypes can cause problems!1. JSON
(JavaScript Object Notation)
What is JSON?
JSON is like a universal language for data. Imagine you want to send a letter to
someone who speaks a different language - JSON is like a translator that both
computers and humans can understand.
Why do we use JSON?
It's lightweight (takes up less space)
Easy to read and write
Most programming languages understand it
Perfect for sending data between servers and web pages
JSON Rules:
Data is in name/value pairs
Data is separated by commas
Objects are in curly braces {}
Arrays are in square brackets []
Strings must use double quotes ""
javascript// JSON Example
const person = {
"name": "Alice",
"age": 25,
"hobbies": ["reading", "swimming"],
"isStudent": false
};
// Converting JavaScript object to JSON string
const jsonString = JSON.stringify(person);
console.log(jsonString); // '{"name":"Alice","age":25,"hobbies":
["reading","swimming"],"isStudent":false}'
// Converting JSON string back to JavaScript object
const backToObject = JSON.parse(jsonString);
console.log(backToObject.name); // "Alice"
2. Promises
What is a Promise?
A Promise is like ordering food at a restaurant. When you order, the waiter gives
you a receipt (the promise). This receipt promises that your food will come
eventually. The food might arrive (resolve), or the kitchen might run out of
ingredients (reject).
Why do we need Promises?
JavaScript is single-threaded, meaning it can only do one thing at a time. When we
need to wait for something (like loading data from the internet), we don't want to
stop everything else. Promises help us handle these waiting situations.
Three States of a Promise:
Pending: Still waiting (food is being cooked)
Fulfilled/Resolved: Success (food arrived)
Rejected: Failed (kitchen ran out of ingredients)
javascript// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
// Simulate waiting for something
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve("Success! Data loaded");
} else {
reject("Error! Something went wrong");
}
}, 1000);
});
// Using the Promise
myPromise
.then(result => {
console.log(result); // If successful
})
.catch(error => {
console.log(error); // If failed
});
// Chaining Promises
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log('Data received:', data);
return processData(data);
})
.then(processedData => {
console.log('Processed:', processedData);
})
.catch(error => {
console.log('Error:', error);
});
3. Fetch
What is Fetch?
Fetch is like a messenger that goes to other websites to get information for you.
It's the modern way to make HTTP requests (ask for data from servers).
Why use Fetch?
Cleaner syntax than older methods
Returns Promises (works well with modern JavaScript)
More flexible and powerful
Built into modern browsers
javascript// Basic Fetch
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(users => {
console.log('Users:', users);
})
.catch(error => {
console.error('Error:', error);
});
// POST request (sending data)
const newUser = {
name: 'John Doe',
email: '
[email protected]'
};
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);
});
// Fetch with error handling
async function fetchUserData(userId) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/$
{userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
4. Working with Real APIs
What is an API?
API stands for Application Programming Interface. Think of it like a waiter in a
restaurant - you (the customer) don't go directly to the kitchen (the database),
but you tell the waiter (API) what you want, and they bring it to you.
Common API Patterns:
REST: Uses HTTP methods (GET, POST, PUT, DELETE)
GraphQL: More flexible, ask for exactly what you need
WebSocket: Real-time communication
javascript// Real API Example: Weather API
const API_KEY = 'your-api-key-here';
const city = 'London';
async function getWeather(city) {
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=$
{API_KEY}&units=metric`
);
if (!response.ok) {
throw new Error(`Weather API error: ${response.status}`);
}
const weatherData = await response.json();
return {
city: weatherData.name,
temperature: weatherData.main.temp,
description: weatherData.weather[0].description,
humidity: weatherData.main.humidity
};
} catch (error) {
console.error('Error fetching weather:', error);
return null;
}
}
// Using the weather function
getWeather('London').then(weather => {
if (weather) {
console.log(`Weather in ${weather.city}: ${weather.temperature}°C, $
{weather.description}`);
}
});
// Handling Rate Limits and Retries
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return response;
}
if (response.status === 429) { // Rate limited
console.log(`Rate limited, waiting before retry ${i + 1}`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
continue;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log(`Attempt ${i + 1} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
5. Lexical Scope
What is Lexical Scope?
Lexical scope is like the rules about who can access what in your house. If you
have a private diary in your bedroom, only you can read it. But if you leave a note
on the kitchen table, everyone in the house can see it.
Key Concepts:
Scope: Where variables can be accessed
Lexical: Determined by where you write the code, not where you run it
Nested scopes: Inner functions can access outer variables, but not vice versa
javascript// Global scope - everyone can access
const globalVar = "I'm global";
function outerFunction() {
// Function scope - only this function and its children can access
const outerVar = "I'm in outer function";
function innerFunction() {
// Inner scope - only this function can access
const innerVar = "I'm in inner function";
// Inner function can access all outer variables
console.log(globalVar); // ✅ Works
console.log(outerVar); // ✅ Works
console.log(innerVar); // ✅ Works
}
innerFunction();
// But outer function can't access inner variables
// console.log(innerVar); // ❌ Error!
}
outerFunction();
// Block scope with let and const
if (true) {
let blockVar = "I'm in a block";
const alsoBlockVar = "Me too";
console.log(blockVar); // ✅ Works here
}
// console.log(blockVar); // ❌ Error! Can't access outside block
// Scope chain example
function grandparent() {
const grandparentVar = "grandparent";
function parent() {
const parentVar = "parent";
function child() {
const childVar = "child";
// Child can access all ancestors
console.log(childVar); // "child"
console.log(parentVar); // "parent"
console.log(grandparentVar); // "grandparent"
}
child();
}
parent();
}
grandparent();
6. Async/Await
What is Async/Await?
Async/await is like having a patient friend who waits for you. Instead of saying
"call me when you're done" (Promises), you can say "I'll wait here until you're
finished" (await).
Why use Async/Await?
Makes asynchronous code look like synchronous code
Easier to read and understand
Better error handling with try/catch
Reduces "callback hell" and "promise chains"
javascript// Promise version (harder to read)
function getUserDataPromise(userId) {
return fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(user => {
return fetch(`/api/posts/${user.id}`)
.then(response => response.json())
.then(posts => {
return { user, posts };
});
})
.catch(error => {
console.error('Error:', error);
throw error;
});
}
// Async/await version (much cleaner!)
async function getUserDataAsync(userId) {
try {
const userResponse = await fetch(`/api/users/${userId}`);
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Using async function
async function displayUserData(userId) {
try {
const { user, posts } = await getUserDataAsync(userId);
console.log('User:', user.name);
console.log('Posts:', posts.length);
} catch (error) {
console.log('Failed to load user data');
}
}
// Parallel async operations
async function fetchMultipleUsers() {
try {
// These run in parallel (at the same time)
const [user1, user2, user3] = await Promise.all([
fetch('/api/users/1').then(r => r.json()),
fetch('/api/users/2').then(r => r.json()),
fetch('/api/users/3').then(r => r.json())
]);
console.log('All users loaded:', [user1, user2, user3]);
} catch (error) {
console.error('Error loading users:', error);
}
}
// Async function always returns a Promise
async function simpleAsync() {
return "Hello World";
}
simpleAsync().then(result => {
console.log(result); // "Hello World"
});
7. DOM Selection
What is the DOM?
DOM stands for Document Object Model. Think of it as a family tree of your webpage.
Every HTML element is a family member, and you can find and talk to any family
member using JavaScript.
Common Selection Methods:
javascript// 1. getElementById - Find one element by its ID
const header = document.getElementById('main-header');
console.log(header);
// 2. getElementsByClassName - Find elements by class name (returns collection)
const buttons = document.getElementsByClassName('btn');
console.log(buttons); // HTMLCollection
// 3. getElementsByTagName - Find elements by tag name
const allParagraphs = document.getElementsByTagName('p');
// 4. querySelector - Find FIRST element that matches CSS selector
const firstButton = document.querySelector('.btn');
const specificButton = document.querySelector('#submit-btn');
const complexSelector = document.querySelector('.container .btn.primary');
// 5. querySelectorAll - Find ALL elements that match CSS selector
const allButtons = document.querySelectorAll('.btn');
const allLinks = document.querySelectorAll('a[href^="https"]');
// Working with NodeList vs HTMLCollection
const nodeList = document.querySelectorAll('.btn'); // NodeList
const htmlCollection = document.getElementsByClassName('btn'); // HTMLCollection
// NodeList has forEach method
nodeList.forEach(button => {
console.log(button.textContent);
});
// HTMLCollection needs to be converted to array
Array.from(htmlCollection).forEach(button => {
console.log(button.textContent);
});
// Advanced selections
const elements = {
// Find element by attribute
emailInput: document.querySelector('input[type="email"]'),
// Find nested elements
navLinks: document.querySelectorAll('nav ul li a'),
// Find siblings
nextSibling: document.querySelector('.first').nextElementSibling,
// Find parent
parent: document.querySelector('.child').parentElement,
// Find children
children: document.querySelector('.parent').children
};
// Checking if element exists before using it
const maybeElement = document.querySelector('.might-not-exist');
if (maybeElement) {
maybeElement.style.color = 'red';
}
8. DOM Basics
What can you do with DOM elements?
Once you find an element, you can change its content, style, attributes, and more.
It's like being able to redecorate your room however you want.
javascript// Getting and setting content
const heading = document.querySelector('h1');
// Different ways to get/set content
console.log(heading.textContent); // Just the text
console.log(heading.innerHTML); // HTML content
console.log(heading.outerHTML); // Element + HTML content
heading.textContent = 'New text content';
heading.innerHTML = '<em>New HTML content</em>';
// Working with attributes
const link = document.querySelector('a');
// Get attributes
console.log(link.href);
console.log(link.getAttribute('href'));
console.log(link.className);
// Set attributes
link.href = 'https://example.com';
link.setAttribute('target', '_blank');
link.className = 'external-link';
// Working with classes
const element = document.querySelector('.box');
// Add class
element.classList.add('active');
// Remove class
element.classList.remove('inactive');
// Toggle class
element.classList.toggle('highlighted');
// Check if class exists
if (element.classList.contains('active')) {
console.log('Element is active');
}
// Working with styles
const box = document.querySelector('.box');
// Set individual styles
box.style.color = 'red';
box.style.backgroundColor = 'blue';
box.style.fontSize = '20px';
// Set multiple styles at once
Object.assign(box.style, {
width: '200px',
height: '100px',
border: '2px solid black'
});
// Creating new elements
const newParagraph = document.createElement('p');
newParagraph.textContent = 'This is a new paragraph';
newParagraph.className = 'dynamic-content';
// Adding elements to the page
const container = document.querySelector('.container');
container.appendChild(newParagraph);
// Inserting elements at specific positions
const firstChild = container.firstElementChild;
container.insertBefore(newParagraph, firstChild);
// Removing elements
const elementToRemove = document.querySelector('.remove-me');
if (elementToRemove) {
elementToRemove.remove(); // Modern way
// elementToRemove.parentNode.removeChild(elementToRemove); // Old way
}
9. Advanced DOM
Advanced DOM manipulation techniques for complex interactions:
javascript// Document Fragments - Efficient way to add multiple elements
function createUserList(users) {
const fragment = document.createDocumentFragment();
users.forEach(user => {
const li = document.createElement('li');
li.innerHTML = `
<span class="name">${user.name}</span>
<span class="email">${user.email}</span>
`;
fragment.appendChild(li);
});
document.querySelector('#user-list').appendChild(fragment);
}
// Template elements
const template = document.querySelector('#user-template');
function createUserFromTemplate(user) {
const clone = template.content.cloneNode(true);
clone.querySelector('.name').textContent = user.name;
clone.querySelector('.email').textContent = user.email;
return clone;
}
// Observer APIs
// 1. Intersection Observer - Know when element enters viewport
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
// Load images, start animations, etc.
}
});
});
document.querySelectorAll('.lazy-load').forEach(el => {
observer.observe(el);
});
// 2. Mutation Observer - Watch for DOM changes
const mutationObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
console.log('Children added/removed');
}
if (mutation.type === 'attributes') {
console.log('Attribute changed:', mutation.attributeName);
}
});
});
mutationObserver.observe(document.body, {
childList: true,
attributes: true,
subtree: true
});
// 3. Resize Observer - Watch for element size changes
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach(entry => {
console.log('Element resized:', entry.target);
console.log('New size:', entry.contentRect);
});
});
resizeObserver.observe(document.querySelector('.resizable'));
// Custom Data Attributes
const element = document.querySelector('.data-element');
// Set custom data
element.dataset.userId = '123';
element.dataset.userName = 'john-doe';
// Get custom data
console.log(element.dataset.userId); // "123"
console.log(element.dataset.userName); // "john-doe"
// Virtual Scrolling for large lists
class VirtualScroller {
constructor(container, items, itemHeight = 50) {
this.container = container;
this.items = items;
this.itemHeight = itemHeight;
this.visibleItems = Math.ceil(container.clientHeight / itemHeight) + 1;
this.startIndex = 0;
this.render();
this.bindEvents();
}
render() {
const visibleItems = this.items.slice(
this.startIndex,
this.startIndex + this.visibleItems
);
this.container.innerHTML = visibleItems.map((item, index) => `
<div class="item" style="transform: translateY(${(this.startIndex + index) *
this.itemHeight}px)">
${item.content}
</div>
`).join('');
}
bindEvents() {
this.container.addEventListener('scroll', () => {
const newStartIndex = Math.floor(this.container.scrollTop / this.itemHeight);
if (newStartIndex !== this.startIndex) {
this.startIndex = newStartIndex;
this.render();
}
});
}
}
10. Events
What are Events?
Events are like notifications that tell your JavaScript code "something happened!"
- a user clicked a button, typed in a text field, or moved their mouse. Your code
can "listen" for these events and respond to them.
javascript// Basic Event Listening
const button = document.querySelector('#my-button');
// Method 1: addEventListener (preferred)
button.addEventListener('click', function() {
console.log('Button clicked!');
});
// Method 2: Arrow function
button.addEventListener('click', () => {
console.log('Button clicked with arrow function!');
});
// Method 3: Named function (good for removing listeners later)
function handleButtonClick() {
console.log('Button clicked with named function!');
}
button.addEventListener('click', handleButtonClick);
// Removing event listeners
button.removeEventListener('click', handleButtonClick);
// Event object - contains information about the event
button.addEventListener('click', function(event) {
console.log('Event type:', event.type); // "click"
console.log('Target element:', event.target); // The button
console.log('Mouse position:', event.clientX, event.clientY);
// Prevent default behavior
event.preventDefault();
// Stop event from bubbling up
event.stopPropagation();
});
// Different types of events
const input = document.querySelector('#text-input');
// Keyboard events
input.addEventListener('keydown', (e) => {
console.log('Key pressed:', e.key);
if (e.key === 'Enter') {
console.log('Enter key pressed!');
}
});
input.addEventListener('keyup', (e) => {
console.log('Key released:', e.key);
});
// Input events
input.addEventListener('input', (e) => {
console.log('Input value changed:', e.target.value);
});
input.addEventListener('change', (e) => {
console.log('Input lost focus, final value:', e.target.value);
});
// Mouse events
const box = document.querySelector('.interactive-box');
box.addEventListener('mouseenter', () => {
console.log('Mouse entered the box');
});
box.addEventListener('mouseleave', () => {
console.log('Mouse left the box');
});
box.addEventListener('mousemove', (e) => {
console.log('Mouse position in box:', e.offsetX, e.offsetY);
});
// Touch events for mobile
box.addEventListener('touchstart', (e) => {
console.log('Touch started');
e.preventDefault(); // Prevent scrolling
});
box.addEventListener('touchmove', (e) => {
console.log('Touch moving');
});
box.addEventListener('touchend', (e) => {
console.log('Touch ended');
});
// Window events
window.addEventListener('resize', () => {
console.log('Window resized:', window.innerWidth, window.innerHeight);
});
window.addEventListener('scroll', () => {
console.log('Page scrolled:', window.scrollY);
});
// Document events
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM is fully loaded');
});
// Event bubbling and capturing
document.querySelector('.parent').addEventListener('click', () => {
console.log('Parent clicked');
});
document.querySelector('.child').addEventListener('click', (e) => {
console.log('Child clicked');
// e.stopPropagation(); // Uncomment to stop bubbling
});
// Event delegation - Handle events for elements that don't exist yet
document.querySelector('#dynamic-list').addEventListener('click', (e) => {
if (e.target.matches('.list-item')) {
console.log('List item clicked:', e.target.textContent);
}
});
11. Forms
Working with Forms
Forms are how users give information to your website. JavaScript helps you
validate, submit, and manipulate form data.
javascript// Getting form elements
const form = document.querySelector('#my-form');
const nameInput = document.querySelector('#name');
const emailInput = document.querySelector('#email');
const submitButton = document.querySelector('#submit');
// Basic form handling
form.addEventListener('submit', function(e) {
e.preventDefault(); // Stop the form from submitting normally
// Get form data
const formData = new FormData(form);
const name = formData.get('name');
const email = formData.get('email');
console.log('Name:', name);
console.log('Email:', email);
// Validate and submit
if (validateForm(name, email)) {
submitForm(formData);
}
});
// Form validation
function validateForm(name, email) {
let isValid = true;
// Clear previous errors
clearErrors();
// Name validation
if (!name || name.trim().length < 2) {
showError(nameInput, 'Name must be at least 2 characters');
isValid = false;
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email || !emailRegex.test(email)) {
showError(emailInput, 'Please enter a valid email');
isValid = false;
}
return isValid;
}
function showError(input, message) {
input.classList.add('error');
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = message;
input.parentNode.appendChild(errorDiv);
}
function clearErrors() {
document.querySelectorAll('.error').forEach(el => {
el.classList.remove('error');
});
document.querySelectorAll('.error-message').forEach(el => {
el.remove();
});
}
// Real-time validation
nameInput.addEventListener('input', function() {
const value = this.value.trim();
if (value.length >= 2) {
this.classList.remove('error');
this.classList.add('valid');
} else {
this.classList.remove('valid');
this.classList.add('error');
}
});
// Working with different input types
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
const radioButtons = document.querySelectorAll('input[type="radio"]');
const selectElement = document.querySelector('select');
// Get all checked checkboxes
function getCheckedValues(name) {
return Array.from(document.querySelectorAll(`input[name="${name}"]:checked`))
.map(cb => cb.value);
}
// Get selected radio button value
function getRadioValue(name) {
const checked = document.querySelector(`input[name="${name}"]:checked`);
return checked ? checked.value : null;
}
// Handle select dropdown
selectElement.addEventListener('change', function() {
console.log('Selected value:', this.value);
console.log('Selected text:', this.options[this.selectedIndex].text);
});
// File upload handling
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', function(e) {
const files = e.target.files;
for (let file of files) {
console.log('File name:', file.name);
console.log('File size:', file.size);
console.log('File type:', file.type);
// Read file content
const reader = new FileReader();
reader.onload = function(e) {
console.log('File content:', e.target.result);
};
reader.readAsText(file);
}
});
// Form submission with fetch
async function submitForm(formData) {
try {
submitButton.disabled = true;
submitButton.textContent = 'Submitting...';
const response = await fetch('/api/submit', {
method: 'POST',
body: formData
});
if (response.ok) {
showSuccess('Form submitted successfully!');
form.reset();
} else {
throw new Error('Submission failed');
}
} catch (error) {
showError(form, 'Submission failed. Please try again.');
} finally {
submitButton.disabled = false;
submitButton.textContent = 'Submit';
}
}
// Dynamic form fields
function addFormField() {
const container = document.querySelector('#dynamic-fields');
const fieldCount = container.children.length;
const newField = document.createElement('div');
newField.innerHTML = `
<input type="text" name="field_${fieldCount}" placeholder="Field ${fieldCount +
1}">
<button type="button" onclick="removeField(this)">Remove</button>
`;
container.appendChild(newField);
}
function removeField(button) {
button.parentNode.remove();
}
12. Package Managers
What is a Package Manager?
A package manager is like a library system for code. Instead of writing everything
from scratch, you can "borrow" code libraries (packages) that other developers have
created and shared.
Why use Package Managers?
Save time by reusing existing code
Automatically handle dependencies (when one library needs another)
Easy to update libraries
Manage different versions of libraries
NPM (Node Package Manager)
bash# Initialize a new project
npm init -y
# Install a package
npm install lodash
# Install a specific version
npm install
[email protected]# Install as development dependency
npm install --save-dev jest
# Install globally
npm install -g nodemon
# Install from package.json
npm install
# Update packages
npm update
# Remove package
npm uninstall lodash
# Check for outdated packages
npm outdated
# Audit for security vulnerabilities
npm audit
npm audit fix
Package.json File:
json{
"name": "my-project",
"version": "1.0.0",
"description": "My awesome project",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"build": "webpack --mode production",
"test": "jest"
},
"dependencies": {
"express": "^4.18.2",
"lodash": "^4.17.21"
},
"devDependencies": {
"jest": "^29.5.0",
"nodemon": "^2.0.22"
}
}
Version Numbers Explained:
1.2.3 - Exact version
^1.2.3 - Compatible version (allows 1.x.x, but not 2.x.x)
~1.2.3 - Approximately equivalent (allows 1.2.x, but not 1.3.x)
Yarn (Alternative to NPM):
bash# Initialize project
yarn init -y
# Add package
yarn add lodash
# Add dev dependency
yarn add --dev jest
# Install dependencies
yarn install
# Remove package
yarn remove lodash
# Update packages
yarn upgrade
Using Packages in Your Code:
javascript// After installing lodash: npm install lodash
const _ = require('lodash'); // Node.js
// or
import _ from 'lodash'; // ES6 modules
// Use the library
const numbers = [1, 2, 3, 4, 5];
const doubled = _.map(numbers, n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// Common useful packages
const axios = require('axios'); // HTTP requests
const moment = require('moment'); // Date manipulation
const validator = require('validator'); // Input validation
// Example usage
axios.get('https://api.example.com/data')
.then(response => console.log(response.data));
const formattedDate = moment().format('YYYY-MM-DD');
const isEmail = validator.isEmail('
[email protected]');
13. Module Bundlers
What is a Module Bundler?
A module bundler is like a smart packing assistant. When you have many JavaScript
files that depend on each other, the bundler combines them into fewer, optimized
files that browsers can load efficiently.
Why do we need Module Bundlers?
Browsers don't understand modern module systems natively
Combines many files into fewer HTTP requests
Optimizes code (removes unused code, minifies)
Handles different file types (CSS, images, etc.)
Enables hot reloading during development
Popular Bundlers:
Webpack (Most Popular):
javascript// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', // Starting point
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader', // Transpile modern JS
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // Handle CSS files
},
{
test: /\.(png|jpg|gif)$/,
use: ['file-loader'] // Handle images
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
devServer: {
contentBase: './dist',
hot: true // Hot reloading
}
};
Vite (Modern and Fast):
javascript// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
root: 'src',
build: {
outDir: '../dist',
rollupOptions: {
input: {
main: 'src/index.html',
admin: 'src/admin.html'
}
}
},
server: {
port: 3000,
hot: true
}
});
Parcel (Zero Configuration):
bash# No config needed! Just run:
npx parcel src/index.html
# For production:
npx parcel build src/index.html
How Bundling Works:
javascript// src/math.js
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// src/utils.js
import { add } from './math.js';
export function calculateTotal(items) {
return items.reduce((total, item) => add(total, item.price), 0);
}
// src/index.js
import { calculateTotal } from './utils.js';
import { multiply } from './math.js';
const items = [
{ name: 'Apple', price: 1.50 },
{ name: 'Banana', price: 0.75 }
];
const total = calculateTotal(items);
const totalWithTax = multiply(total, 1.08);
console.log(`Total with tax: ${totalWithTax.toFixed(2)}`);
// Bundler combines all these files into one optimized bundle.js
14. Modules
What are Modules?
Modules are like LEGO blocks for code. Each module is a separate piece that does
one thing well, and you can combine different modules to build complex
applications.
Why use Modules?
Organization: Keep related code together
Reusability: Use the same code in different places
Maintainability: Easier to fix and update code
Namespace: Avoid naming conflicts
ES6 Modules (Modern JavaScript):
javascript// math.js - Exporting functions
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// Default export
export default function multiply(a, b) {
return a * b;
}
// You can also export like this:
const divide = (a, b) => a / b;
const PI = 3.14159;
export { divide, PI };
// main.js - Importing functions
import multiply from './math.js'; // Default import
import { add, subtract, PI } from './math.js'; // Named imports
import { divide as div } from './math.js'; // Import with alias
import * as MathUtils from './math.js'; // Import everything
// Using the imported functions
console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
console.log(multiply(5, 3)); // 15
console.log(div(10, 2)); // 5
console.log(PI); // 3.14159
// Using namespace import
console.log(MathUtils.add(1, 2));
console.log(MathUtils.default(3, 4)); // Default export via namespace
CommonJS Modules (Node.js):
javascript// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// Exporting
module.exports = {
add,
subtract
};
// Or export individually
exports.multiply = (a, b) => a * b;
// main.js
const { add, subtract } = require('./math.js');
const math = require('./math.js');
console.log(add(5, 3));
console.log(math.multiply(4, 2));
Module Patterns:
javascript// 1. Utility Module
// utils.js
export const formatCurrency = (amount) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
};
export const debounce = (func, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(null, args), delay);
};
};
// 2. Class Module
// user.js
export class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
getDisplayName() {
return `${this.name} (${this.email})`;
}
}
// 3. Configuration Module
// config.js
const config = {
API_BASE_URL: process.env.API_URL || 'http://localhost:3000',
MAX_RETRIES: 3,
CACHE_DURATION: 5 * 60 * 1000 // 5 minutes
};
export default config;
// 4. Factory Module
// apiClient.js
export function createApiClient(baseURL) {
return {
async get(endpoint) {
const response = await fetch(`${baseURL}${endpoint}`);
return response.json();
},
async post(endpoint, data) {
const response = await fetch(`${baseURL}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.json();
}
};
}
// Using modules together
// app.js
import { formatCurrency, debounce } from './utils.js';
import { User } from './user.js';
import config from './config.js';
import { createApiClient } from './apiClient.js';
const api = createApiClient(config.API_BASE_URL);
const user = new User('John Doe', '
[email protected]');
console.log(user.getDisplayName());
console.log(formatCurrency(29.99));
const debouncedSearch = debounce((query) => {
api.get(`/search?q=${query}`);
}, 300);
15. Dynamic Imports
What are Dynamic Imports?
Dynamic imports are like ordering food when you're hungry, instead of buying all
groceries at once. You load code only when you actually need it, which makes your
app start faster.
Why use Dynamic Imports?
Code Splitting: Load code only when needed
Performance: Faster initial page load
Conditional Loading: Load features based on user actions
Lazy Loading: Load heavy components on demand
javascript// Static import (loaded immediately)
import { heavyLibrary } from './heavy-library.js';
// Dynamic import (loaded when needed)
async function loadHeavyFeature() {
try {
const { heavyLibrary } = await import('./heavy-library.js');
return heavyLibrary;
} catch (error) {
console.error('Failed to load heavy feature:', error);
}
}
// Real-world examples
// 1. Conditional feature loading
async function initializeApp() {
const userAgent = navigator.userAgent;
if (userAgent.includes('Mobile')) {
const { MobileUI } = await import('./mobile-ui.js');
return new MobileUI();
} else {
const { DesktopUI } = await import('./desktop-ui.js');
return new DesktopUI();
}
}
// 2. Route-based code splitting
const routes = {
'/home': () => import('./pages/home.js'),
'/about': () => import('./pages/about.js'),
'/contact': () => import('./pages/contact.js'),
'/dashboard': () => import('./pages/dashboard.js')
};
async function navigateTo(path) {
const loadPage = routes[path];
if (loadPage) {
try {
const page = await loadPage();
page.default.render();
} catch (error) {
console.error('Failed to load page:', error);
}
}
}
// 3. Feature flags with dynamic imports
class FeatureManager {
constructor(features) {
this.enabledFeatures = features;
}
async loadFeature(featureName) {
if (!this.enabledFeatures.includes(featureName)) {
console.log(`Feature ${featureName} is disabled`);
return null;
}
try {
const feature = await import(`./features/${featureName}.js`);
return feature.default;
} catch (error) {
console.error(`Failed to load feature ${featureName}:`, error);
return null;
}
}
}
const featureManager = new FeatureManager(['analytics', 'chat']);
// Load analytics only if enabled
document.addEventListener('DOMContentLoaded', async () => {
const analytics = await featureManager.loadFeature('analytics');
if (analytics) {
analytics.initialize();
}
});
// 4. Lazy loading with user interaction
class ImageGallery {
constructor() {
this.lightboxLoaded = false;
}
async openLightbox(imageSrc) {
if (!this.lightboxLoaded) {
try {
const { Lightbox } = await import('./lightbox.js');
this.lightbox = new Lightbox();
this.lightboxLoaded = true;
} catch (error) {
console.error('Failed to load lightbox:', error);
return;
}
}
this.lightbox.show(imageSrc);
}
}
// 5. Dynamic imports with error handling and fallbacks
async function loadChartLibrary() {
try {
// Try to load the preferred chart library
const { Chart } = await import('./chart-library.js');
return Chart;
} catch (error) {
console.warn('Primary chart library failed, loading fallback');
try {
// Fallback to simpler chart library
const { SimpleChart } = await import('./simple-chart.js');
return SimpleChart;
} catch (fallbackError) {
console.error('All chart libraries failed to load');
// Return a mock implementation
return {
render: () => console.log('Charts not available')
};
}
}
}
// 6. Progressive enhancement with dynamic imports
class AdvancedTextEditor {
constructor(textarea) {
this.textarea = textarea;
this.enhanced = false;
}
async enhance() {
if (this.enhanced) return;
try {
const [
{ SyntaxHighlighter },
{ AutoComplete },
{ SpellChecker }
] = await Promise.all([
import('./syntax-highlighter.js'),
import('./auto-complete.js'),
import('./spell-checker.js')
]);
this.syntaxHighlighter = new SyntaxHighlighter(this.textarea);
this.autoComplete = new AutoComplete(this.textarea);
this.spellChecker = new SpellChecker(this.textarea);
this.enhanced = true;
console.log('Text editor enhanced with advanced features');
} catch (error) {
console.error('Failed to enhance text editor:', error);
}
}
}
// Usage
const editor = new AdvancedTextEditor(document.querySelector('#code-editor'));
document.querySelector('#enhance-btn').addEventListener('click', () => {
editor.enhance();
});
16. ECMAScript
What is ECMAScript?
ECMAScript (ES) is like the rulebook for JavaScript. Just like how sports have
official rules that everyone follows, ECMAScript defines the official standards and
features for JavaScript.
JavaScript Evolution Timeline:
javascript// ES5 (2009) - The foundation most people learned
var name = 'John';
var numbers = [1, 2, 3, 4, 5];
// Old way to iterate
for (var i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
// Old way to create objects with methods
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return 'Hello, I am ' + this.name;
};
// ES6/ES2015 (2015) - Major update!
const name = 'John';
const numbers = [1, 2, 3, 4, 5];
// Arrow functions
const greet = (name) => `Hello, ${name}!`;
// Template literals
const message = `Welcome, ${name}. You have ${numbers.length} items.`;
// Classes
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I am ${this.name}`;
}
}
// Destructuring
const [first, second, ...rest] = numbers;
const { name: personName, age } = person;
// For...of loops
for (const number of numbers) {
console.log(number);
}
// Promises
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
// ES2016 (ES7) - Small but useful additions
// Exponentiation operator
const squared = 2 ** 8; // 256 (instead of Math.pow(2, 8))
// Array.includes()
const fruits = ['apple', 'banana', 'orange'];
const hasApple = fruits.includes('apple'); // true
// ES2017 (ES8) - Async/await revolution
// Async/await
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
return user;
} catch (error) {
console.error('Error:', error);
}
}
// Object.entries() and Object.values()
const user = { name: 'John', age: 30, city: 'New York' };
const entries = Object.entries(user); // [['name', 'John'], ['age', 30], ['city',
'New York']]
const values = Object.values(user); // ['John', 30, 'New York']
// String padding
const id = '123';
const paddedId = id.padStart(6, '0'); // '000123'
// ES2018 (ES9) - Advanced features
// Rest/spread for objects
const baseUser = { name: 'John', age: 30 };
const fullUser = { ...baseUser, city: 'New York', country: 'USA' };
const { name, ...otherProps } = fullUser;
// Async iteration
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
for await (const value of asyncGenerator()) {
console.log(value); // 1, 2, 3
}
// ES2019 (ES10) - Quality of life improvements
// Array.flat() and Array.flatMap()
const nested = [[1, 2], [3, 4], [5, 6]];
const flattened = nested.flat(); // [1, 2, 3, 4, 5, 6]
const doubled = nested.flatMap(arr => arr.map(x => x * 2)); // [2, 4, 6, 8, 10, 12]
// Object.fromEntries()
const entries = [['name', 'John'], ['age', 30]];
const obj = Object.fromEntries(entries); // { name: 'John', age: 30 }
// String.trimStart() and String.trimEnd()
const text = ' hello world ';
console.log(text.trimStart()); // 'hello world '
console.log(text.trimEnd()); // ' hello world'
// ES2020 (ES11) - Modern conveniences
// Optional chaining
const user = { profile: { name: 'John' } };
const userName = user?.profile?.name; // 'John'
const userAge = user?.profile?.age; // undefined (no error!)
// Nullish coalescing
const defaultName = userName ?? 'Guest'; // Only uses 'Guest' if userName is null
or undefined
// BigInt for large numbers
const bigNumber = 123456789012345678901234567890n;
const anotherBig = BigInt('123456789012345678901234567890');
// Dynamic imports (covered earlier)
const module = await import('./my-module.js');
// ES2021 (ES12) - Recent additions
// String.replaceAll()
const text = 'hello world hello';
const replaced = text.replaceAll('hello', 'hi'); // 'hi world hi'
// Logical assignment operators
let value = null;
value ??= 'default'; // Assigns only if value is null or undefined
value ||= 'fallback'; // Assigns if value is falsy
value &&= 'modified'; // Assigns if value is truthy
// Promise.any() - succeeds when any promise resolves
const promises = [
fetch('/api/server1'),
fetch('/api/server2'),
fetch('/api/server3')
];
try {
const firstSuccess = await Promise.any(promises);
console.log('First server responded:', firstSuccess);
} catch (error) {
console.log('All servers failed');
}
// ES2022 (ES13) - Latest features
// Class fields
class MyClass {
// Public field
publicField = 'I am public';
// Private field
#privateField = 'I am private';
// Private method
#privateMethod() {
return this.#privateField;
}
getPrivate() {
return this.#privateMethod();
}
}
// Top-level await (in modules)
// You can now use await at the top level of modules
const data = await fetch('/api/config').then(r => r.json());
console.log(data);
// Array.at() - negative indexing
const arr = [1, 2, 3, 4, 5];
console.log(arr.at(-1)); // 5 (last item)
console.log(arr.at(-2)); // 4 (second to last)
Using Modern JavaScript Today:
javascript// Babel configuration for older browser support
// .babelrc
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions"]
}
}]
]
}
// Modern JavaScript best practices
class DataManager {
#cache = new Map();
async fetchData(url) {
// Use optional chaining and nullish coalescing
const cached = this.#cache.get(url);
if (cached?.timestamp && Date.now() - cached.timestamp < 300000) {
return cached.data;
}
try {
const response = await fetch(url);
const data = await response.json();
this.#cache.set(url, {
data,
timestamp: Date.now()
});
return data;
} catch (error) {
console.error(`Failed to fetch ${url}:`, error);
throw error;
}
}
// Using modern array methods
processUsers(users) {
return users
.filter(user => user?.isActive)
.map(user => ({
...user,
displayName: user.name ?? 'Unknown User'
}))
.sort((a, b) => a.name.localeCompare(b.name));
}
}
17. Legacy var
What's wrong with var?
var is like an old car - it works, but it has some quirky behaviors that can cause
problems. Modern JavaScript uses let and const instead because they're more
predictable.
Problems with var:
javascript// Problem 1: Function scope (not block scope)
function demonstrateVarScope() {
if (true) {
var message = "Hello"; // var is function-scoped
let blockMessage = "Hi"; // let is block-scoped
}
console.log(message); // "Hello" - var is accessible outside the block!
// console.log(blockMessage); // Error! let is not accessible outside the block
}
// Problem 2: Hoisting confusion
console.log(hoistedVar); // undefined (not an error!)
var hoistedVar = "I'm hoisted";
// What actually happens:
// var hoistedVar; // Declaration is hoisted to the top
// console.log(hoistedVar); // undefined
// hoistedVar = "I'm hoisted"; // Assignment stays in place
// Compare with let:
// console.log(hoistedLet); // ReferenceError!
// let hoistedLet = "I cause an error";
// Problem 3: Loop variable issues
console.log("=== Var Loop Problem ===");
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log("var:", i); // Prints 3, 3, 3 (not 0, 1, 2!)
}, 100);
}
// Fixed with let:
console.log("=== Let Loop Fix ===");
for (let j = 0; j < 3; j++) {
setTimeout(() => {
console.log("let:", j); // Prints 0, 1, 2 as expected
}, 100);
}
// Problem 4: Redeclaration allowed
var name = "John";
var name = "Jane"; // No error! But this might be a mistake
console.log(name); // "Jane"
// With let/const:
let safeName = "John";
// let safeName = "Jane"; // SyntaxError! Can't redeclare
// Problem 5: Global object pollution
var globalVar = "I'm global";
console.log(window.globalVar); // "I'm global" (in browsers)
let betterGlobal = "I'm also global";
console.log(window.betterGlobal); // undefined (doesn't pollute global object)
// Legacy var patterns and how to modernize them
// Old pattern: IIFE to create scope
(function() {
var privateVar = "I'm private";
// ... code that uses privateVar
})();
// Modern: Just use block scope
{
let privateVar = "I'm private";
// ... code that uses privateVar
}
// Old pattern: Variable declarations at top
function oldStyle() {
var i, len, result, items;
items = getItems();
result = [];
for (i = 0, len = items.length; i < len; i++) {
if (items[i].isValid) {
result.push(items[i]);
}
}
return result;
}
// Modern: Declare variables when needed
function modernStyle() {
const items = getItems();
const result = [];
for (let i = 0; i < items.length; i++) {
if (items[i].isValid) {
result.push(items[i]);
}
}
return result;
}
// Even more modern: Use array methods
function veryModernStyle() {
return getItems().filter(item => item.isValid);
}
// When you might still see var:
// 1. Legacy codebases
// 2. Some specific hoisting needs (rare)
// 3. Compatibility with very old JavaScript engines
// Migration strategy from var to let/const
function migrateFromVar() {
// Step 1: Replace var with let for variables that change
let counter = 0;
for (let i = 0; i < 10; i++) {
counter += i;
}
// Step 2: Replace var with const for variables that don't change
const API_URL = 'https://api.example.com';
const users = fetchUsers();
// Step 3: Use const by default, let when you need to reassign
const userName = getUserName(); // Won't change
let status = 'loading'; // Will change to 'loaded' or 'error'
fetchUserData()
.then(data => {
status = 'loaded';
})
.catch(error => {
status = 'error';
});
}
1. Legacy Topics 📚
What are Legacy Topics?
Legacy topics are like old toys that still work but aren't the best way to play
anymore. In JavaScript, these are older ways of doing things that modern JavaScript
has better alternatives for.
Example Legacy vs Modern:
javascript// LEGACY WAY (old, avoid this)
var name = "John"; // var has problems
function oldFunction() {
// function declarations can be messy
}
// MODERN WAY (new, better)
const name = "John"; // const/let are safer
const newFunction = () => {
// arrow functions are cleaner
}
Why Legacy Matters:
You'll see old code everywhere
Understanding legacy helps you fix old websites
Job interviews often ask about differences
Key Legacy Topics to Know:
var vs let/const
function declarations vs arrow functions
Callbacks vs Promises/async-await
2. Window Object 🪟
What is the Window Object?
Think of the window object like the control panel of your web browser. It's the
biggest, most important object that controls everything you see and do on a
webpage.
javascript// The window object is everywhere!
console.log(window); // Shows the entire browser control panel
// These are all the same thing:
window.alert("Hello!");
alert("Hello!"); // window is implied
window.console.log("Hi!");
console.log("Hi!"); // window is implied
What's Inside the Window?
javascript// Browser information
console.log(window.navigator.userAgent); // What browser you're using
console.log(window.location.href); // Current webpage address
// Screen information
console.log(window.screen.width); // Your screen width
console.log(window.screen.height); // Your screen height
// Control the browser
window.open("https://google.com"); // Open new tab
window.close(); // Close current tab
window.back(); // Go back in history
Window Methods (Things Window Can Do):
javascript// Talk to the user
window.alert("Important message!"); // Pop-up message
let answer = window.confirm("Are you sure?"); // Yes/No question
let name = window.prompt("What's your name?"); // Ask for input
// Control timing
window.setTimeout(() => {
console.log("This runs after 2 seconds");
}, 2000);
window.setInterval(() => {
console.log("This runs every 3 seconds");
}, 3000);
Why Window Object Matters:
Controls everything in the browser
Lets you interact with users
Manages timing and events
Stores global variables
3. Closures 🎁
What is a Closure?
A closure is like a magic backpack that a function carries with it. This backpack
contains all the variables from where the function was created, and the function
can always reach into this backpack to use those variables, even when it's far away
from home.
Simple Closure Example:
javascriptfunction outerFunction(x) {
// This is like packing a backpack
function innerFunction(y) {
// This function carries the backpack with 'x' inside
return x + y; // Can still use 'x' from the backpack!
}
return innerFunction; // Give away the function with its backpack
}
const addFive = outerFunction(5); // Create function with x=5 in backpack
console.log(addFive(3)); // 8 (uses 5 from backpack + 3)
Real-World Closure Example:
javascriptfunction createCounter() {
let count = 0; // This goes in the backpack
return function() {
count++; // Reach into backpack and change count
return count;
};
}
const counter1 = createCounter(); // First counter with its own backpack
const counter2 = createCounter(); // Second counter with its own backpack
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (different backpack!)
console.log(counter1()); // 3
Why Closures are Powerful:
javascript// Private variables (like a secret diary)
function createBankAccount(initialBalance) {
let balance = initialBalance; // Private - no one else can touch this!
return {
deposit: function(amount) {
balance += amount;
return balance;
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
} else {
return "Insufficient funds!";
}
},
getBalance: function() {
return balance;
}
};
}
const myAccount = createBankAccount(100);
console.log(myAccount.deposit(50)); // 150
console.log(myAccount.withdraw(30)); // 120
console.log(myAccount.balance); // undefined (private!)
Key Points:
Functions remember where they were born
They carry their "birth environment" with them
Great for creating private variables
Used everywhere in JavaScript frameworks
4. Loops & Iteration 🔄
What is a Loop?
A loop is like telling someone to do the same thing over and over until you say
stop. It's like a broken record that keeps playing the same song.
For Loop (The Classic Counter):
javascript// Like counting from 1 to 5
for (let i = 1; i <= 5; i++) {
console.log(`Count: ${i}`);
}
// Output: Count: 1, Count: 2, Count: 3, Count: 4, Count: 5
// Loop through an array
const fruits = ["apple", "banana", "orange"];
for (let i = 0; i < fruits.length; i++) {
console.log(`Fruit ${i + 1}: ${fruits[i]}`);
}
While Loop (Keep Going Until...):
javascriptlet energy = 10;
while (energy > 0) {
console.log(`I have ${energy} energy left!`);
energy--; // Use up energy
}
console.log("I'm tired now!");
For...Of Loop (Go Through Each Item):
javascriptconst colors = ["red", "green", "blue"];
// Easy way to go through each item
for (const color of colors) {
console.log(`I love the color ${color}`);
}
// Works with strings too!
for (const letter of "HELLO") {
console.log(letter); // H, E, L, L, O
}
For...In Loop (Go Through Object Properties):
javascriptconst person = {
name: "Alice",
age: 25,
city: "New York"
};
for (const key in person) {
console.log(`${key}: ${person[key]}`);
}
// Output: name: Alice, age: 25, city: New York
Array Methods (Modern Loops):
javascriptconst numbers = [1, 2, 3, 4, 5];
// forEach - do something with each item
numbers.forEach(num => {
console.log(`Number: ${num}`);
});
// map - transform each item
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - keep only items that match
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
// find - get the first item that matches
const found = numbers.find(num => num > 3);
console.log(found); // 4
Loop Control:
javascriptfor (let i = 1; i <= 10; i++) {
if (i === 3) {
continue; // Skip this round, go to next
}
if (i === 7) {
break; // Stop the loop completely
}
console.log(i); // 1, 2, 4, 5, 6
}
5. The Event Loop ⚡
What is the Event Loop?
The Event Loop is like a super organized waiter in a busy restaurant. JavaScript
can only do one thing at a time (like a waiter can only carry one order at a time),
but the Event Loop helps manage multiple tasks by organizing them in a smart way.
How JavaScript Works:
javascriptconsole.log("1. First thing");
setTimeout(() => {
console.log("3. This happens later");
}, 0);
console.log("2. Second thing");
// Output order: 1, 2, 3 (even though timeout is 0!)
The Call Stack (The Main Kitchen):
javascriptfunction first() {
console.log("First function");
second();
}
function second() {
console.log("Second function");
third();
}
function third() {
console.log("Third function");
}
first();
// The functions stack up like plates, then execute from top to bottom
Callback Queue (The Waiting Area):
javascriptconsole.log("Start");
// These go to the waiting area first
setTimeout(() => console.log("Timeout 1"), 0);
setTimeout(() => console.log("Timeout 2"), 0);
// This happens immediately
console.log("End");
// Output: Start, End, Timeout 1, Timeout 2
Promises and Microtasks (VIP Queue):
javascriptconsole.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
// Output: Start, End, Promise, Timeout
// Promises get VIP treatment!
Real-World Example:
javascriptfunction simulateAPI() {
console.log("1. Making API call...");
setTimeout(() => {
console.log("4. API response received!");
}, 2000);
console.log("2. API call sent, continuing other work...");
// Other work continues immediately
for (let i = 0; i < 3; i++) {
console.log(`3. Doing other work ${i + 1}`);
}
}
simulateAPI();
Key Points:
JavaScript is single-threaded (one thing at a time)
Event Loop manages the order of operations
Synchronous code runs first
Asynchronous code waits in queues
Promises have higher priority than timeouts
6. Intro to Regex (Regular Expressions) 🔍
What is Regex?
Regex is like a super smart search tool that can find patterns in text. It's like
having a detective that can find specific clues in a huge pile of papers.
Basic Regex Patterns:
javascriptconst text = "My phone number is 123-456-7890";
// Simple search
const simplePattern = /phone/;
console.log(simplePattern.test(text)); // true (found "phone")
// Case insensitive search
const casePattern = /PHONE/i; // 'i' means ignore case
console.log(casePattern.test(text)); // true
// Find phone number pattern
const phonePattern = /\d{3}-\d{3}-\d{4}/; // \d means digit, {3} means 3 times
console.log(phonePattern.test(text)); // true
Common Regex Symbols:
javascript// . (dot) - matches any character
/c.t/.test("cat"); // true
/c.t/.test("cut"); // true
/c.t/.test("cot"); // true
// * - zero or more of previous character
/colou*r/.test("color"); // true
/colou*r/.test("colour"); // true
// + - one or more of previous character
/colou+r/.test("color"); // false (no 'u')
/colou+r/.test("colour"); // true
// ? - zero or one of previous character
/colou?r/.test("color"); // true
/colou?r/.test("colour"); // true
// ^ - start of string
/^Hello/.test("Hello world"); // true
/^Hello/.test("Say Hello"); // false
// $ - end of string
/world$/.test("Hello world"); // true
/world$/.test("world is big"); // false
Practical Regex Examples:
javascript// Email validation (simple)
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
console.log(emailPattern.test("[email protected]")); // true
console.log(emailPattern.test("invalid-email")); // false
// Extract all numbers from text
const text = "I have 5 cats, 3 dogs, and 12 fish";
const numbers = text.match(/\d+/g); // 'g' means global (find all)
console.log(numbers); // ["5", "3", "12"]
// Replace patterns
const sentence = "I love cats and cats love me";
const newSentence = sentence.replace(/cats/g, "dogs");
console.log(newSentence); // "I love dogs and dogs love me"
// Split by pattern
const data = "apple,banana;orange:grape";
const fruits = data.split(/[,;:]/); // Split by comma, semicolon, or colon
console.log(fruits); // ["apple", "banana", "orange", "grape"]
Character Classes:
javascript// \d - any digit (0-9)
// \w - any word character (a-z, A-Z, 0-9, _)
// \s - any whitespace (space, tab, newline)
const text = "Hello World 123";
console.log(text.match(/\d/g)); // ["1", "2", "3"]
console.log(text.match(/\w/g)); // ["H", "e", "l", "l", "o", "W", "o", "r", "l",
"d", "1", "2", "3"]
console.log(text.match(/\s/g)); // [" ", " "]
7. Interview Questions 🎯
Common Closure Questions:
javascript// TRICKY QUESTION: What does this print?
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // What prints?
}, 100);
}
// Answer: 3, 3, 3 (because var is function-scoped)
// HOW TO FIX IT:
for (let i = 0; i < 3; i++) { // Use 'let' instead of 'var'
setTimeout(() => {
console.log(i); // Now prints: 0, 1, 2
}, 100);
}
// OR fix with closure:
for (var i = 0; i < 3; i++) {
(function(j) { // Create closure with current value
setTimeout(() => {
console.log(j); // Prints: 0, 1, 2
}, 100);
})(i);
}
Hoisting Questions:
javascript// What happens here?
console.log(myVar); // undefined (not an error!)
var myVar = 5;
// This is what JavaScript actually sees:
var myVar; // Declaration is hoisted
console.log(myVar); // undefined
myVar = 5; // Assignment stays in place
// Function hoisting:
sayHello(); // Works! Prints "Hello"
function sayHello() {
console.log("Hello");
}
// But this doesn't work:
sayGoodbye(); // Error!
var sayGoodbye = function() {
console.log("Goodbye");
};
This Keyword Questions:
javascriptconst person = {
name: "Alice",
greet: function() {
console.log(`Hello, I'm ${this.name}`);
},
greetArrow: () => {
console.log(`Hello, I'm ${this.name}`); // 'this' is undefined!
}
};
person.greet(); // "Hello, I'm Alice"
person.greetArrow(); // "Hello, I'm undefined"
// Bind, call, apply:
const greetFunc = person.greet;
greetFunc(); // "Hello, I'm undefined" (lost context)
const boundGreet = person.greet.bind(person);
boundGreet(); // "Hello, I'm Alice" (context restored)
Array Method Questions:
javascript// What's the difference?
const numbers = [1, 2, 3];
// forEach - returns undefined
const result1 = numbers.forEach(n => n * 2);
console.log(result1); // undefined
// map - returns new array
const result2 = numbers.map(n => n * 2);
console.log(result2); // [2, 4, 6]
// Original array unchanged
console.log(numbers); // [1, 2, 3]
8. Map and Set 📊
What is a Map?
A Map is like a super-powered object that can use ANY type of key (not just
strings), remembers the order you added things, and has useful methods.
javascript// Regular object (limited)
const regularObj = {
"name": "Alice",
"age": 25
};
// Map (super-powered)
const myMap = new Map();
// You can use ANY type as a key!
myMap.set("name", "Alice"); // String key
myMap.set(42, "The answer"); // Number key
myMap.set(true, "Boolean key"); // Boolean key
myMap.set({id: 1}, "Object key"); // Object key!
console.log(myMap.get("name")); // "Alice"
console.log(myMap.get(42)); // "The answer"
console.log(myMap.size); // 4 (easy to get size!)
Map Methods:
javascriptconst fruits = new Map();
// Adding items
fruits.set("apple", 5);
fruits.set("banana", 3);
fruits.set("orange", 8);
// Getting items
console.log(fruits.get("apple")); // 5
console.log(fruits.has("banana")); // true
console.log(fruits.size); // 3
// Removing items
fruits.delete("banana");
console.log(fruits.has("banana")); // false
// Loop through Map
for (const [fruit, count] of fruits) {
console.log(`${fruit}: ${count}`);
}
// Get all keys or values
console.log([...fruits.keys()]); // ["apple", "orange"]
console.log([...fruits.values()]); // [5, 8]
// Clear everything
fruits.clear();
console.log(fruits.size); // 0
When to Use Map vs Object:
javascript// Use Object when:
const userSettings = {
theme: "dark",
language: "english",
notifications: true
};
// Use Map when:
const userScores = new Map();
userScores.set("player1", 100);
userScores.set("player2", 85);
userScores.set("player3", 92);
// Maps are better for:
// - Frequent additions/deletions
// - Non-string keys
// - When you need to know the size
// - When order matters
What is a Set?
A Set is like a collection box that never has duplicates. If you try to add the
same thing twice, it just ignores the second one.
javascript// Regular array (allows duplicates)
const regularArray = [1, 2, 2, 3, 3, 3];
console.log(regularArray); // [1, 2, 2, 3, 3, 3]
// Set (no duplicates allowed!)
const mySet = new Set([1, 2, 2, 3, 3, 3]);
console.log(mySet); // Set {1, 2, 3}
Set Methods:
javascriptconst colors = new Set();
// Adding items
colors.add("red");
colors.add("blue");
colors.add("green");
colors.add("red"); // Won't be added again!
console.log(colors.size); // 3 (not 4!)
console.log(colors.has("red")); // true
// Remove items
colors.delete("blue");
console.log(colors.has("blue")); // false
// Loop through Set
for (const color of colors) {
console.log(color); // "red", "green"
}
// Convert to array
const colorArray = [...colors];
console.log(colorArray); // ["red", "green"]
// Clear everything
colors.clear();
console.log(colors.size); // 0
Practical Set Examples:
javascript// Remove duplicates from array
const numbersWithDuplicates = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbersWithDuplicates)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
// Track unique visitors
const visitors = new Set();
visitors.add("user123");
visitors.add("user456");
visitors.add("user123"); // Won't be added again
console.log(`Unique visitors: ${visitors.size}`); // 2
// Find common elements
const set1 = new Set([1, 2, 3, 4]);
const set2 = new Set([3, 4, 5, 6]);
const intersection = new Set([...set1].filter(x => set2.has(x)));
console.log(intersection); // Set {3, 4}
9. Generator Functions ⚡
What is a Generator Function?
A Generator Function is like a magic function that can pause and resume. It's like
a video that you can play, pause, play again, pause again, etc. Each time you
"play" it, it continues from where it left off.
javascript// Regular function - runs all at once
function regularFunction() {
console.log("Step 1");
console.log("Step 2");
console.log("Step 3");
return "Done!";
}
regularFunction(); // Prints all steps immediately
// Generator function - can pause and resume
function* generatorFunction() {
console.log("Step 1");
yield "Paused after step 1"; // Pause here!
console.log("Step 2");
yield "Paused after step 2"; // Pause here too!
console.log("Step 3");
return "All done!";
}
const gen = generatorFunction(); // Creates a generator (doesn't run yet)
console.log(gen.next()); // Run until first yield
console.log(gen.next()); // Continue to next yield
console.log(gen.next()); // Continue to end
How Generators Work:
javascriptfunction* countToThree() {
console.log("Starting to count...");
yield 1; // Pause and return 1
console.log("Continuing...");
yield 2; // Pause and return 2
console.log("Almost done...");
yield 3; // Pause and return 3
console.log("Finished!");
return "Done counting!";
}
const counter = countToThree();
console.log(counter.next()); // {value: 1, done: false}
console.log(counter.next()); // {value: 2, done: false}
console.log(counter.next()); // {value: 3, done: false}
console.log(counter.next()); // {value: "Done counting!", done: true}
Infinite Generators:
javascriptfunction* infiniteNumbers() {
let num = 1;
while (true) { // Infinite loop!
yield num;
num++;
}
}
const numbers = infiniteNumbers();
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
console.log(numbers.next().value); // 3
// Could go on forever!
Practical Generator Examples:
javascript// ID Generator
function* createIDGenerator() {
let id = 1;
while (true) {
yield `ID_${id}`;
id++;
}
}
const idGen = createIDGenerator();
console.log(idGen.next().value); // "ID_1"
console.log(idGen.next().value); // "ID_2"
console.log(idGen.next().value); // "ID_3"
// Fibonacci Generator
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
// Output: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
// Processing Large Data Sets
function* processLargeDataSet(data) {
for (let i = 0; i < data.length; i++) {
// Process one item at a time
const processed = data[i] * 2;
yield processed;
}
}
const largeData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const processor = processLargeDataSet(largeData);
// Process items one by one (memory efficient!)
for (const result of processor) {
console.log(result); // 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
}
Generator with For...Of:
javascriptfunction* colors() {
yield "red";
yield "green";
yield "blue";
}
// Generators work with for...of loops!
for (const color of colors()) {
console.log(color); // "red", "green", "blue"
}
// Convert generator to array
const colorArray = [...colors()];
console.log(colorArray); // ["red", "green", "blue"]
Advanced Generator Features:
javascriptfunction* interactiveGenerator() {
const name = yield "What's your name?";
const age = yield `Hello ${name}! What's your age?`;
yield `Nice to meet you ${name}, age ${age}!`;
}
const chat = interactiveGenerator();
console.log(chat.next().value); // "What's your name?"
console.log(chat.next("Alice").value); // "Hello Alice! What's your age?"
console.log(chat.next(25).value); // "Nice to meet you Alice, age 25!"
Why Generators are Useful:
Memory efficient (process one item at a time)
Can create infinite sequences
Great for iterating through large datasets
Useful for creating custom iterators
Help with asynchronous programming patterns