š Introduction to JavaScript
JavaScript is a high-level, just-in-time compiled, multi-paradigm programming language that powers the interactive web. Created in 1995 by Brendan Eich, it has evolved from a simple client-side scripting language to a robust ecosystem powering both frontend and backend applications.
šÆ Core Characteristics
JavaScript is dynamically typed, meaning variables can hold any type without explicit declarations. It features first-class functions, prototypal inheritance, and an event-driven, non-blocking I/O model.
š Execution Model & Event Loop
JavaScript uses a single-threaded event loop architecture. The call stack manages function execution, while Web APIs handle asynchronous operations.
console.log("JavaScript Advanced Track Loaded");
// First-class function example
const greet = (name) => `Hello, ${name}!`;
// Higher-order function
function executeFunction(fn, value) {
return fn(value);
}
const result = executeFunction(greet, "CodeOrbitPro");
console.log(result);
// Closure example
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());⨠ES6+ Features Overview
ECMAScript 2015 (ES6) marked the largest update to JavaScript. It introduced block-scoped variables, arrow functions, template literals, destructuring, classes, promises, and modules.
šÆ Why ES6 Matters
Before ES6, JavaScript lacked block scoping, proper class syntax, and modular system. ES6 addressed these issues and transformed JavaScript into a professional-grade language.
// ES6+ Feature Examples
// Block-scoped variables
{
let blockScoped = "I'm block scoped";
const constantValue = "I cannot be reassigned";
}
// Arrow functions
const add = (a, b) => a + b;
const square = x => x * x;
// Template literals
const name = "CodeOrbitPro";
const message = `Welcome to ${name}!`;
// Destructuring
const [first, second] = [10, 20];
const {username, email} = {username: "admin", email: "admin@example.com"};
// Spread operator
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
// Rest parameters
function sumAll(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(message);
console.log("Sum:", sumAll(1, 2, 3, 4, 5));š¦ let & const: Block Scoping
let and const introduced block-level scoping to JavaScript. Unlike var (function-scoped), let/const are confined to the block where they're declared.
š Temporal Dead Zone (TDZ)
The TDZ is the period between entering a scope and the actual declaration. Accessing variables during TDZ throws ReferenceError, preventing bugs.
ā” const: Immutable Binding
const creates a read-only reference. For objects/arrays, the contents remain mutable, but the binding cannot be reassigned.
// let and const examples
// 1. Basic usage
let score = 85;
const pi = 3.1415;
score = 90; // OK - let allows reassignment
// pi = 3.14; // Error - const prevents reassignment
// 2. Block scoping
function demonstrateScope() {
if (true) {
let blockScoped = "I exist only in this block";
const blockConstant = "I'm also block-scoped";
var functionScoped = "I'm function-scoped";
}
// console.log(blockScoped); // Error
// console.log(blockConstant); // Error
console.log(functionScoped); // Works
}
// 3. Temporal Dead Zone
// console.log(tdzVariable); // ReferenceError
let tdzVariable = "I'm in TDZ until this line";
// 4. const with objects and arrays
const person = { name: "Alice", age: 25 };
person.age = 26; // OK - modifying property
// person = { name: "Bob" }; // Error
const numbers = [1, 2, 3];
numbers.push(4); // OK
// numbers = [5, 6, 7]; // Error
// 5. Loop with let (new binding each iteration)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log("let:", i), 100); // 0,1,2
}
// 6. Loop with var (problematic)
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log("var:", j), 100); // 3,3,3
}
demonstrateScope();š¹ Arrow Functions: Lexical this
Arrow functions provide concise syntax and solve the this binding problem. They inherit this from the surrounding lexical scope.
šÆ Lexical this Binding
Regular functions have dynamic this based on how they're called. Arrow functions capture this from where they're defined, making them ideal for callbacks.
ā ļø Limitations
Arrow functions cannot be used as constructors, don't have their own arguments object, and lack a prototype property.
// Arrow function examples
// Basic syntax
const add = (a, b) => a + b;
const multiply = (x, y) => {
return x * y;
};
const square = n => n * n;
const getRandom = () => Math.random();
// Lexical this binding
const counter = {
count: 0,
incrementArrow: function() {
setTimeout(() => {
this.count++;
console.log("Arrow:", this.count);
}, 100);
},
incrementRegular: function() {
setTimeout(function() {
this.count++;
console.log("Regular:", this.count);
}, 100);
}
};
counter.incrementArrow(); // Works correctly
// counter.incrementRegular(); // Fails - wrong this
// Array methods with arrows
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);
console.log("Doubled:", doubled);
console.log("Evens:", evens);
console.log("Sum:", sum);
// No arguments object
const showArgs = (...args) => {
console.log("Arguments:", args);
};
showArgs(1, 2, 3);š Template Literals: Enhanced Strings
Template literals use backticks and support embedded expressions with ${expression} syntax. They preserve whitespace and line breaks naturally.
š·ļø Tagged Templates
Tagged templates allow custom string processing. The tag function receives string parts and interpolated values for advanced manipulation like safe HTML escaping.
// Template literal examples
// Basic interpolation
const name = "CodeOrbitPro";
const message = `Hello, ${name}!`;
console.log(message);
// Expression evaluation
const a = 10, b = 20;
const calculation = `Sum of ${a} and ${b} is ${a + b}`;
// Multi-line strings
const multiLine = `
This is a multi-line string.
It preserves line breaks.
Great for HTML templates!
`;
// Function calls
const dateMessage = `Today is ${new Date().toLocaleDateString()}`;
// Tagged template for safe HTML
function safeHtml(strings, ...values) {
const escape = (str) => {
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
};
return strings.reduce((result, string, i) => {
return result + string + (values[i] ? escape(values[i]) : '');
}, '');
}
const userInput = "<script>alert('test')</script>";
const safeOutput = safeHtml`User content: ${userInput}`;
console.log("Safe HTML:", safeOutput);šÆ Destructuring: Pattern Matching
Destructuring extracts values from arrays or objects into variables. Arrays use positional matching; objects use property name matching.
š Nested Destructuring
You can destructure nested objects and arrays, providing default values and renaming variables for cleaner code.
// Destructuring examples
// Array destructuring
const numbers = [10, 20, 30, 40, 50];
const [first, second, third] = numbers;
console.log(first, second, third);
// Skipping elements
const [, , thirdOnly] = numbers;
console.log("Third only:", thirdOnly);
// Rest operator with arrays
const [head, ...tail] = numbers;
console.log("Head:", head);
console.log("Tail:", tail);
// Object destructuring
const user = {
id: 1,
name: "Alice",
email: "alice@example.com",
age: 25,
address: {
city: "New York",
country: "USA"
}
};
const { name, email, age } = user;
console.log(name, email, age);
// Renaming variables
const { name: userName, email: userEmail } = user;
// Nested destructuring
const { address: { city, country } } = user;
console.log("Location:", city, country);
// Default values
const { phone = "Not provided" } = user;
// Function parameter destructuring
function displayUser({ name, email, age = 18 }) {
console.log(`Name: ${name}, Email: ${email}, Age: ${age}`);
}
displayUser(user);
// Swapping variables
let a = 5, b = 10;
[a, b] = [b, a];
console.log("Swapped:", a, b);šļø Object-Oriented Programming in JS
JavaScript implements OOP through prototype-based inheritance, enhanced with ES6 class syntax. Classes are syntactic sugar over constructor functions and prototypes.
šļø Four Pillars of OOP
Encapsulation (private fields), Inheritance (extends), Polymorphism (method overriding), and Abstraction (abstract class patterns).
// OOP in JavaScript
// ES6 Class syntax
class Vehicle {
#mileage = 0;
constructor(name, wheels) {
this.name = name;
this.wheels = wheels;
}
info() {
console.log(`${this.name} has ${this.wheels} wheels.`);
}
get mileage() {
return this.#mileage;
}
set mileage(value) {
if (value >= 0) this.#mileage = value;
}
static vehicleType() {
return "Generic Vehicle";
}
}
// Inheritance
class Car extends Vehicle {
constructor(name, wheels, brand) {
super(name, wheels);
this.brand = brand;
}
info() {
console.log(`${this.brand} ${this.name} has ${this.wheels} wheels.`);
}
drive() {
console.log(`Driving the ${this.brand} ${this.name}`);
}
}
class ElectricCar extends Car {
constructor(name, wheels, brand, battery) {
super(name, wheels, brand);
this.battery = battery;
}
drive() {
console.log(`Silently driving the electric ${this.brand} ${this.name}`);
}
charge() {
console.log(`Charging ${this.battery} kWh battery`);
}
}
// Usage
const myCar = new Car('Sedan', 4, 'Toyota');
myCar.info();
myCar.drive();
myCar.mileage = 15000;
console.log("Mileage:", myCar.mileage);
const tesla = new ElectricCar('Model 3', 4, 'Tesla', 75);
tesla.info();
tesla.drive();
tesla.charge();
console.log(Vehicle.vehicleType());šļø Classes & Inheritance Deep Dive
ES6 classes provide a clean syntax for OOP with extends, super, and method overriding. Advanced patterns include abstract classes, mixins, and method chaining.
šÆ Abstract Class Pattern
Simulate abstract classes by throwing errors in methods that must be implemented by subclasses, or checking new.target in constructors.
// Classes & Inheritance Advanced
// Abstract class pattern
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error("Cannot instantiate abstract class");
}
}
area() {
throw new Error("Method 'area()' must be implemented");
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
const circle = new Circle(5);
console.log(`Circle area: ${circle.area().toFixed(2)}`);
// Method chaining (fluent interface)
class QueryBuilder {
constructor() {
this.query = { select: [], from: '', where: [] };
}
select(...fields) {
this.query.select.push(...fields);
return this;
}
from(table) {
this.query.from = table;
return this;
}
where(condition) {
this.query.where.push(condition);
return this;
}
build() {
let sql = `SELECT ${this.query.select.join(', ') || '*'} FROM ${this.query.from}`;
if (this.query.where.length) {
sql += ` WHERE ${this.query.where.join(' AND ')}`;
}
return sql;
}
}
const query = new QueryBuilder()
.select('name', 'email')
.from('users')
.where('age > 18')
.build();
console.log("Query:", query);
// Extending built-in objects
class ObservableArray extends Array {
constructor(...items) {
super(...items);
this.listeners = [];
}
subscribe(listener) {
this.listeners.push(listener);
}
notify() {
this.listeners.forEach(listener => listener(this));
}
push(...items) {
const result = super.push(...items);
this.notify();
return result;
}
}
const arr = new ObservableArray(1, 2, 3);
arr.subscribe(data => console.log("Array changed:", data));
arr.push(4);š Prototypes: JavaScript's Inheritance Core
Prototypes are the foundation of JavaScript inheritance. Every object has an internal [[Prototype]] link, forming a prototype chain for property lookup.
š Prototype Chain
When accessing a property, JavaScript checks the object first, then traverses the prototype chain until found or reaching null.
// Prototypes in JavaScript
// Constructor function and prototype
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
const alice = new Person('Alice', 25);
const bob = new Person('Bob', 30);
alice.greet();
bob.greet();
// Prototype chain inspection
console.log(alice.__proto__ === Person.prototype);
console.log(Person.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__); // null
// Object.create() for inheritance
const animal = {
eat() {
console.log(`${this.name} is eating`);
},
sleep() {
console.log(`${this.name} is sleeping`);
}
};
const dog = Object.create(animal);
dog.name = "Buddy";
dog.bark = function() {
console.log(`${this.name} barks!`);
};
dog.eat();
dog.bark();
// hasOwnProperty vs in operator
const example = { prop: 'value' };
console.log(example.hasOwnProperty('prop')); // true
console.log('toString' in example); // true (inherited)
console.log(example.hasOwnProperty('toString')); // false
// Prototypal inheritance without classes
function Shape(color) {
this.color = color;
}
Shape.prototype.getColor = function() {
return this.color;
};
function Circle(color, radius) {
Shape.call(this, color);
this.radius = radius;
}
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
Circle.prototype.getArea = function() {
return Math.PI * this.radius ** 2;
};
const redCircle = new Circle('red', 5);
console.log(redCircle.getColor());
console.log(redCircle.getArea().toFixed(2));š Encapsulation: Private Fields & Closures
Encapsulation hides internal state and exposes controlled interfaces. ES2022 introduced private fields (# prefix) for true privacy.
š Private Fields (#)
Private fields cannot be accessed outside the class, providing genuine encapsulation. Getters and setters enable controlled access.
// Encapsulation in JavaScript
// Private fields (ES2022)
class BankAccount {
#balance;
#transactions = [];
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
this.#transactions.push({ type: 'DEPOSIT', amount });
console.log(`Deposited: $${amount}`);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
this.#transactions.push({ type: 'WITHDRAWAL', amount });
console.log(`Withdrawn: $${amount}`);
return amount;
}
console.log('Insufficient funds');
return 0;
}
get balance() {
return this.#balance;
}
get formattedBalance() {
return `$${this.#balance.toFixed(2)}`;
}
getTransactionCount() {
return this.#transactions.length;
}
}
const account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(`Balance: ${account.formattedBalance}`);
console.log(`Transactions: ${account.getTransactionCount()}`);
// Getters and setters with validation
class User {
constructor(email, password) {
this._email = email;
this._password = password;
}
get email() {
return this._email;
}
set email(newEmail) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (emailRegex.test(newEmail)) {
this._email = newEmail;
} else {
throw new Error('Invalid email');
}
}
get password() {
return '********';
}
set password(newPassword) {
if (newPassword.length >= 8) {
this._password = newPassword;
} else {
throw new Error('Password too short');
}
}
authenticate(input) {
return this._password === input;
}
}
const user = new User('john@example.com', 'secure123');
user.email = 'john.doe@example.com';
console.log(user.email);
console.log(user.authenticate('secure123'));
// Closure-based encapsulation
function createCounter() {
let count = 0;
return {
increment() {
count++;
console.log(`Count: ${count}`);
},
getCount() {
return count;
},
reset() {
count = 0;
}
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount());ā” Asynchronous JavaScript
Asynchronous JavaScript enables non-blocking operations using callbacks, promises, and async/await. The event loop manages execution order.
š Event Loop Priority
Microtasks (Promise callbacks) execute before macrotasks (setTimeout, I/O). Understanding this prevents race conditions.
// Asynchronous JavaScript Examples
console.log("Start");
// Promise (microtask)
Promise.resolve().then(() => {
console.log("Microtask: Promise");
});
// setTimeout (macrotask)
setTimeout(() => {
console.log("Macrotask: Timeout");
}, 0);
console.log("End");
// Promise creation
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.3;
if (success) {
resolve("Success!");
} else {
reject(new Error("Failed!"));
}
}, 1000);
});
promise
.then(result => console.log(result))
.catch(error => console.error(error.message))
.finally(() => console.log("Promise settled"));
// Promise.all
const promises = [
new Promise(resolve => setTimeout(() => resolve("Data 1"), 400)),
new Promise(resolve => setTimeout(() => resolve("Data 2"), 200)),
new Promise(resolve => setTimeout(() => resolve("Data 3"), 600))
];
Promise.all(promises)
.then(results => console.log("All:", results));
// Promise.race
Promise.race(promises)
.then(first => console.log("First:", first));š¤ Promises: Async Operation Handlers
Promises represent the eventual completion of async operations. They have three states: pending, fulfilled, and rejected.
š Promise Chaining
Each .then() returns a new promise, enabling sequential async operations with clean error handling via .catch().
// Promises Comprehensive Examples
// Creating promises
const fetchData = (shouldSucceed) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldSucceed) {
resolve({ id: 1, name: "John Doe" });
} else {
reject(new Error("Failed to fetch data"));
}
}, 500);
});
};
// Consuming promises
fetchData(true)
.then(data => {
console.log("Data:", data);
return data.name.toUpperCase();
})
.then(upperName => {
console.log("Uppercase:", upperName);
return upperName.length;
})
.then(length => {
console.log("Length:", length);
})
.catch(error => {
console.error("Error:", error.message);
})
.finally(() => {
console.log("Operation complete");
});
// Promise static methods
const resolved = Promise.resolve("Immediate resolution");
resolved.then(v => console.log(v));
const rejected = Promise.reject(new Error("Immediate rejection"));
rejected.catch(e => console.error(e.message));
// Promise.all - wait for all
const userPromise = Promise.resolve({ name: "Alice" });
const postsPromise = Promise.resolve(["Post 1", "Post 2"]);
const commentsPromise = Promise.resolve(["Comment 1"]);
Promise.all([userPromise, postsPromise, commentsPromise])
.then(([user, posts, comments]) => {
console.log("User:", user);
console.log("Posts:", posts);
console.log("Comments:", comments);
});
// Promise.allSettled - all complete regardless of failure
const mixed = [
Promise.resolve("Success"),
Promise.reject(new Error("Failure")),
Promise.resolve("Another success")
];
Promise.allSettled(mixed)
.then(results => {
results.forEach((result, i) => {
if (result.status === "fulfilled") {
console.log(`Promise ${i}: ${result.value}`);
} else {
console.log(`Promise ${i}: ${result.reason.message}`);
}
});
});ā³ Async/Await: Syntactic Sugar for Promises
Async/await makes promise-based code look synchronous. Functions marked with async return promises; await pauses execution until resolution.
šÆ Error Handling
Use try/catch blocks for error handling in async functions, providing cleaner code than .catch() chains.
// Async/Await Examples
// Helper delay function
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Basic async function
async function fetchUserData() {
console.log("Fetching data...");
const data = await new Promise(resolve => {
setTimeout(() => resolve({ id: 1, name: "John Doe" }), 1000);
});
console.log("Data received:", data);
return data;
}
fetchUserData().then(user => console.log("Returned:", user.name));
// Error handling with try/catch
async function riskyOperation() {
try {
const result = await new Promise((resolve, reject) => {
const shouldFail = Math.random() > 0.5;
setTimeout(() => {
if (shouldFail) {
reject(new Error("Operation failed"));
} else {
resolve("Success!");
}
}, 500);
});
console.log("Result:", result);
return result;
} catch (error) {
console.error("Caught:", error.message);
return "Fallback value";
}
}
riskyOperation().then(result => console.log("Final:", result));
// Sequential vs parallel execution
async function sequentialExample() {
console.log("\nSequential:");
const result1 = await delay(300).then(() => "Result 1");
console.log(result1);
const result2 = await delay(200).then(() => "Result 2");
console.log(result2);
const result3 = await delay(100).then(() => "Result 3");
console.log(result3);
}
async function parallelExample() {
console.log("\nParallel:");
const [result1, result2, result3] = await Promise.all([
delay(300).then(() => "Result 1"),
delay(200).then(() => "Result 2"),
delay(100).then(() => "Result 3")
]);
console.log(result1, result2, result3);
}
sequentialExample().then(() => parallelExample());
// Async class methods
class ApiClient {
async get(endpoint) {
await delay(100);
return { data: `Data from ${endpoint}` };
}
async getUserWithPosts(userId) {
const [user, posts] = await Promise.all([
this.get(`users/${userId}`),
this.get(`users/${userId}/posts`)
]);
return { user, posts };
}
}
const client = new ApiClient();
client.getUserWithPosts(1).then(result => console.log("API:", result));š Callbacks: The Foundation of Async JS
Callbacks are functions passed to other functions for later execution. They're the foundation of asynchronous programming in JavaScript.
ā ļø Callback Hell
Deeply nested callbacks create "pyramid of doom" - hard to read and maintain. Modern code uses promises or async/await to avoid this.
// Callback Examples
// Basic callback
function greet(name, callback) {
console.log(`Hello, ${name}!`);
callback();
}
function sayGoodbye() {
console.log("Goodbye!");
}
greet("Alice", sayGoodbye);
// Callback with parameters
function calculate(a, b, operation, callback) {
const result = operation(a, b);
callback(result);
}
function add(x, y) { return x + y; }
function showResult(result) {
console.log(`Result: ${result}`);
}
calculate(5, 3, add, showResult);
// Asynchronous callback
function fetchData(callback) {
console.log("Fetching...");
setTimeout(() => {
const data = { id: 1, name: "John" };
callback(null, data);
}, 1000);
}
function handleData(error, data) {
if (error) {
console.error("Error:", error);
} else {
console.log("Data:", data);
}
}
fetchData(handleData);
// Error-first callback pattern (Node.js style)
function readFile(filename, callback) {
setTimeout(() => {
const success = Math.random() > 0.3;
if (success) {
callback(null, `Contents of ${filename}`);
} else {
callback(new Error(`Failed to read ${filename}`), null);
}
}, 500);
}
readFile("document.txt", (error, content) => {
if (error) {
console.error("Error:", error.message);
} else {
console.log("Content:", content);
}
});
// Array methods with callbacks
const numbers = [1, 2, 3, 4, 5];
numbers.forEach(function(num, i) {
console.log(`Index ${i}: ${num}`);
});
const doubled = numbers.map(function(num) {
return num * 2;
});
console.log("Doubled:", doubled);
const evens = numbers.filter(function(num) {
return num % 2 === 0;
});
console.log("Evens:", evens);
const sum = numbers.reduce(function(total, num) {
return total + num;
}, 0);
console.log("Sum:", sum);š³ DOM & Events: Interactive Web Pages
The Document Object Model (DOM) represents HTML structure. JavaScript manipulates DOM elements and responds to user events.
šÆ Event Delegation
Attach event listeners to parent elements to handle events for multiple children - improves performance and handles dynamic content.
// DOM and Events Examples
// Creating elements
const newDiv = document.createElement('div');
newDiv.textContent = "Dynamic Content";
newDiv.className = "dynamic-box";
newDiv.style.padding = "20px";
newDiv.style.backgroundColor = "#f0f0f0";
newDiv.style.borderRadius = "8px";
document.body.appendChild(newDiv);
// Event handling
const button = document.createElement('button');
button.textContent = "Click Me";
button.addEventListener('click', function(event) {
console.log("Button clicked!");
this.textContent = "Clicked!";
this.disabled = true;
});
button.addEventListener('mouseenter', () => {
button.style.backgroundColor = "#4CAF50";
button.style.color = "white";
});
button.addEventListener('mouseleave', () => {
button.style.backgroundColor = "";
button.style.color = "";
});
document.body.appendChild(button);
// Keyboard events
const input = document.createElement('input');
input.type = "text";
input.placeholder = "Type something...";
input.style.margin = "10px 0";
input.addEventListener('keydown', (event) => {
console.log(`Key: ${event.key}`);
});
input.addEventListener('input', (event) => {
console.log("Value:", event.target.value);
});
document.body.appendChild(input);
// Form events
const form = document.createElement('form');
form.innerHTML = `
`;
form.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log("Form data:", data);
form.reset();
});
document.body.appendChild(form);
// Event delegation
const list = document.createElement('ul');
for (let i = 1; i <= 5; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
li.className = "list-item";
li.style.padding = "8px";
li.style.cursor = "pointer";
list.appendChild(li);
}
list.addEventListener('click', (event) => {
if (event.target.classList.contains('list-item')) {
console.log(`Clicked: ${event.target.textContent}`);
event.target.style.backgroundColor = "#ffeb3b";
}
});
document.body.appendChild(list);
// DOMContentLoaded event
document.addEventListener('DOMContentLoaded', () => {
console.log("DOM fully loaded!");
});š¦ ES6 Modules: Code Organization
ES6 modules allow splitting code into reusable, independent files. They provide encapsulation, prevent global namespace pollution, and enable tree-shaking for optimized builds.
š¤ Export Types
Named exports allow multiple exports per module: export const name = ... or export { name, version }. Default exports export a single value: export default function() {}.
š„ Import Syntax
Named imports: import { add, multiply } from './math.js'. Default imports: import logger from './logger.js'. Namespace imports: import * as utils from './utils.js'.
ā” Dynamic Imports
import() returns a promise and enables code splitting - loading modules only when needed. Essential for performance optimization in large applications.
šÆ Module Features
- Strict mode by default - modules automatically use strict mode
- Module scope - variables aren't global, preventing conflicts
- Deferred execution - modules execute after document parsing
- CORS-enabled - modules require proper CORS headers
- Single execution - modules are executed only once and cached
šļø Module Patterns
Before ES6 modules, developers used IIFE (Immediately Invoked Function Expressions) for encapsulation. The Revealing Module Pattern exposed only what was needed while keeping internals private.
š§ Module Bundlers
Tools like Webpack, Rollup, and Vite bundle multiple modules into optimized production files, handling dependencies, code splitting, and tree shaking.
Understanding Module Resolution
Relative paths (./, ../) resolve within the project. Absolute paths resolve from the server root. Package imports (like 'react', 'lodash') resolve from node_modules via module bundlers.
Dynamic imports are crucial for lazy loading - loading heavy modules only when needed, significantly improving initial page load time. This pattern is used in React's React.lazy() and Vue's defineAsyncComponent().
// ============== MODULE EXAMPLES ==============
// Note: To run these examples, create separate .js files and use a module-aware server
// ============== File: math.js (Named Exports) ==============
/*
// Named exports - can have multiple
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => b !== 0 ? a / b : NaN;
export const PI = 3.14159;
export const E = 2.71828;
// Named export with declaration
export function square(x) {
return x * x;
}
// Export list at the end
const version = "1.0.0";
const author = "CodeOrbitPro";
export { version, author };
*/
// ============== File: logger.js (Default Export) ==============
/*
// Default export - only one per module
export default function log(message, level = 'INFO') {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${level}] ${message}`);
}
// Can also combine default with named exports
export const LOG_LEVELS = {
INFO: 'INFO',
WARN: 'WARN',
ERROR: 'ERROR',
DEBUG: 'DEBUG'
};
*/
// ============== File: api.js (Mixed Exports) ==============
/*
const BASE_URL = 'https://api.example.com';
export async function fetchUsers() {
const response = await fetch(`${BASE_URL}/users`);
return response.json();
}
export async function fetchPosts() {
const response = await fetch(`${BASE_URL}/posts`);
return response.json();
}
export default {
BASE_URL,
fetchUsers,
fetchPosts
};
*/
// ============== File: index.js (Barrel Export) ==============
/*
// Re-exporting - create a single entry point
export { add, subtract, multiply, divide, PI } from './math.js';
export { default as log, LOG_LEVELS } from './logger.js';
export { default as api, fetchUsers, fetchPosts } from './api.js';
*/
// ============== Importing Examples ==============
console.log("========== MODULE IMPORT PATTERNS ==========");
// 1. Named imports (from math.js)
// import { add, subtract, PI, square } from './math.js';
// console.log(add(5, 3)); // 8
// console.log(subtract(10, 4)); // 6
// console.log(PI); // 3.14159
// console.log(square(4)); // 16
// 2. Default import (from logger.js)
// import log from './logger.js';
// log("Application started");
// log("User logged in", "INFO");
// 3. Mixed imports
// import api, { fetchUsers, fetchPosts } from './api.js';
// fetchUsers().then(users => console.log(users));
// 4. Renaming imports
// import { add as sum, subtract as difference } from './math.js';
// console.log(sum(10, 5)); // 15
// 5. Namespace import (import everything)
// import * as math from './math.js';
// console.log(math.add(2, 3));
// console.log(math.PI);
// 6. Combining default and named imports
// import log, { LOG_LEVELS } from './logger.js';
// log("Debug message", LOG_LEVELS.DEBUG);
// ============== Dynamic Imports (Code Splitting) ==============
// Load module only when needed
async function loadMathModule() {
console.log("\n=== Dynamic Import Demo ===");
try {
// Dynamic import returns a promise
const math = await import('./math.js');
console.log("Module loaded dynamically!");
console.log("2 + 3 =", math.add(2, 3));
console.log("PI =", math.PI);
} catch (error) {
console.error("Failed to load module:", error);
}
}
// Uncomment to test dynamic import
// loadMathModule();
// ============== IIFE Module Pattern (Pre-ES6) ==============
// Used before native modules existed
const Calculator = (function() {
// Private variables
let operationCount = 0;
let history = [];
// Private functions
function logOperation(operation, result) {
history.push({
operation,
result,
timestamp: new Date()
});
operationCount++;
}
// Public API
return {
add: function(a, b) {
const result = a + b;
logOperation(`${a} + ${b}`, result);
return result;
},
subtract: function(a, b) {
const result = a - b;
logOperation(`${a} - ${b}`, result);
return result;
},
multiply: function(a, b) {
const result = a * b;
logOperation(`${a} * ${b}`, result);
return result;
},
getHistory: function() {
return [...history]; // Return copy to prevent modification
},
getOperationCount: function() {
return operationCount;
},
clearHistory: function() {
history = [];
operationCount = 0;
console.log("History cleared");
}
};
})();
console.log("\n=== IIFE Module Pattern ===");
console.log("Add 5 + 3:", Calculator.add(5, 3));
console.log("Subtract 10 - 4:", Calculator.subtract(10, 4));
console.log("Multiply 6 * 7:", Calculator.multiply(6, 7));
console.log("Operation count:", Calculator.getOperationCount());
console.log("History:", Calculator.getHistory());
// ============== Revealing Module Pattern ==============
const UserService = (function() {
// Private data
let users = [];
let currentUser = null;
// Private methods
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
function findUserByEmail(email) {
return users.find(u => u.email === email);
}
// Public methods (revealed)
function addUser(name, email) {
if (!validateEmail(email)) {
throw new Error("Invalid email");
}
if (findUserByEmail(email)) {
throw new Error("User already exists");
}
const user = { id: Date.now(), name, email };
users.push(user);
return user;
}
function getUsers() {
return [...users];
}
function login(email) {
const user = findUserByEmail(email);
if (!user) {
throw new Error("User not found");
}
currentUser = user;
return user;
}
function logout() {
currentUser = null;
}
function getCurrentUser() {
return currentUser ? { ...currentUser } : null;
}
// Reveal public API
return {
addUser,
getUsers,
login,
logout,
getCurrentUser
};
})();
console.log("\n=== Revealing Module Pattern ===");
UserService.addUser("Alice", "alice@example.com");
UserService.addUser("Bob", "bob@example.com");
console.log("All users:", UserService.getUsers());
UserService.login("alice@example.com");
console.log("Current user:", UserService.getCurrentUser());
UserService.logout();
// ============== Module Bundler Concept ==============
console.log("\n=== Module Bundler Output (Conceptual) ===");
/*
// Webpack/Rollup/Vite bundle modules into a single file:
(function(modules) {
const installedModules = {};
function require(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
const module = installedModules[moduleId] = {
exports: {}
};
modules[moduleId](module, module.exports, require);
return module.exports;
}
return require(0);
})([
// Entry point
function(module, exports, require) {
const math = require(1);
console.log(math.add(2, 3));
},
// math.js
function(module, exports) {
exports.add = (a, b) => a + b;
exports.PI = 3.14159;
}
]);
*/
// ============== Import Meta ==============
// In modules, you can access module metadata
if (typeof import !== 'undefined' && import.meta) {
console.log("Module URL:", import.meta.url);
}
// ============== Module Best Practices ==============
/*
1. Use named exports for multiple values, default exports for the main value
2. Keep modules focused - single responsibility principle
3. Use index.js files (barrel exports) to simplify imports
4. Prefer named exports over default for better IDE autocomplete
5. Use dynamic imports for code splitting and lazy loading
6. Avoid circular dependencies - they make code hard to maintain
7. Use relative paths for internal modules, package names for external
8. In Node.js, use .mjs extension or "type": "module" in package.json
*/
console.log("\nā
Module examples complete!");
console.log("š¦ To test ES6 modules, create separate .js files and use a local server");
console.log("š” Module bundlers like Webpack handle imports for production");šØ Design Patterns: Reusable Solutions
Design patterns are proven solutions to common software design problems. They provide templates for writing maintainable, scalable code.
š Pattern Categories
Creational: Singleton, Factory, Builder - object creation patterns. Structural: Decorator, Adapter, Proxy - object composition. Behavioral: Observer, Strategy, Command - object interaction.
// Design Patterns in JavaScript
// 1. Singleton Pattern - Ensures single instance
class DatabaseConnection {
static instance;
constructor() {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.connectionId = Math.random().toString(36);
DatabaseConnection.instance = this;
}
query(sql) {
console.log(`Executing: ${sql} (Connection: ${this.connectionId})`);
}
}
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
console.log("Same instance:", db1 === db2);
// 2. Factory Pattern - Creates objects without specifying class
class Button {
render() { console.log("Rendering button"); }
}
class Input {
render() { console.log("Rendering input"); }
}
class Checkbox {
render() { console.log("Rendering checkbox"); }
}
class UIFactory {
createElement(type) {
switch(type) {
case 'button': return new Button();
case 'input': return new Input();
case 'checkbox': return new Checkbox();
default: throw new Error(`Unknown type: ${type}`);
}
}
}
const factory = new UIFactory();
const btn = factory.createElement('button');
btn.render();
// 3. Observer Pattern - Pub/Sub for event handling
class EventBus {
constructor() {
this.events = {};
}
subscribe(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// Return unsubscribe function
return () => {
this.events[event] = this.events[event].filter(cb => cb !== callback);
};
}
publish(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(callback => callback(data));
}
}
const bus = new EventBus();
const unsubscribe = bus.subscribe('user-login', (user) => {
console.log(`User logged in: ${user.name}`);
});
bus.publish('user-login', { name: 'Alice' });
unsubscribe();
bus.publish('user-login', { name: 'Bob' }); // Won't trigger
// 4. Decorator Pattern - Add behavior dynamically
class Coffee {
cost() { return 5; }
description() { return "Coffee"; }
}
class MilkDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() { return this.coffee.cost() + 2; }
description() { return `${this.coffee.description()}, milk`; }
}
class SugarDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() { return this.coffee.cost() + 1; }
description() { return `${this.coffee.description()}, sugar`; }
}
let myCoffee = new Coffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new SugarDecorator(myCoffee);
console.log(`${myCoffee.description()}: $${myCoffee.cost()}`);
// 5. Strategy Pattern - Swap algorithms at runtime
const paymentStrategies = {
credit: (amount) => console.log(`Paid $${amount} with credit card`),
paypal: (amount) => console.log(`Paid $${amount} with PayPal`),
crypto: (amount) => console.log(`Paid $${amount} with cryptocurrency`)
};
class Checkout {
constructor(strategy) {
this.strategy = strategy;
}
pay(amount) {
this.strategy(amount);
}
setStrategy(strategy) {
this.strategy = strategy;
}
}
const checkout = new Checkout(paymentStrategies.credit);
checkout.pay(100);
checkout.setStrategy(paymentStrategies.paypal);
checkout.pay(50);