JavaScript Advanced

šŸš€ 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.

šŸ“Œ Single-threaded Event LoopšŸ“Œ First-class FunctionsšŸ“Œ Prototypal Inheritance
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.

šŸ“Œ Block ScopingšŸ“Œ Arrow FunctionsšŸ“Œ Native Modules
// 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.

šŸ“Œ Block ScopešŸ“Œ Temporal Dead ZonešŸ“Œ No Redeclaration
// 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.

šŸ“Œ Lexical thisšŸ“Œ Implicit ReturnšŸ“Œ No arguments object
// 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.

šŸ“Œ Expression InterpolationšŸ“Œ Multi-line SupportšŸ“Œ Tagged Templates
// 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.

šŸ“Œ Array DestructuringšŸ“Œ Object DestructuringšŸ“Œ Nested Patterns
// 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).

šŸ“Œ Prototype ChainšŸ“Œ ES6 ClassesšŸ“Œ Private Fields
// 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.

šŸ“Œ extends & superšŸ“Œ Abstract ClassesšŸ“Œ Method Chaining
// 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.

šŸ“Œ [[Prototype]] LinkšŸ“Œ Constructor FunctionsšŸ“Œ Object.create()
// 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.

šŸ“Œ Private FieldsšŸ“Œ Getters/SettersšŸ“Œ Closure Encapsulation
// 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.

šŸ“Œ CallbacksšŸ“Œ PromisesšŸ“Œ Async/Await
// 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().

šŸ“Œ Promise StatesšŸ“Œ ChainingšŸ“Œ Error Propagation
// 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 FunctionsšŸ“Œ Await KeywordšŸ“Œ Try/Catch
// 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.

šŸ“Œ Higher-order FunctionsšŸ“Œ Error-First PatternšŸ“Œ Callback Hell
// 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 ManipulationšŸ“Œ Event ListenersšŸ“Œ Event Propagation
// 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.

šŸ“Œ Named Exports šŸ“Œ Default Exports šŸ“Œ Dynamic Imports šŸ“Œ Code Splitting šŸ“Œ 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.

šŸ“Œ SingletonšŸ“Œ FactoryšŸ“Œ ObserveršŸ“Œ Decorator
// 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);