40 Intermediate JavaScript Tutorials
1. Prototypes
Prototypes enable inheritance in JavaScript objects.
Example: Adding a method to a prototype.
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return `Hello, ${this.name}!`;
};
const alice = new Person("Alice");
console.log(alice.greet());
Note: All instances share prototype methods.
2. Closures in Depth
Closures allow functions to retain access to outer scope variables even after execution.
Example: Private counter.
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
console.log(counter.increment());
console.log(counter.getCount());
Note: Closures are useful for data privacy.
3. IIFE
Immediately Invoked Function Expressions (IIFE) run immediately and create private scopes.
Example: IIFE for initialization.
(function() {
const secret = "Hidden";
console.log(secret);
})();
Note: IIFEs prevent global namespace pollution.
4. This Keyword
The this keyword refers to the context of a function's execution.
Example: this in an object method.
const person = {
name: "Bob",
greet() {
return `Hello, ${this.name}!`;
}
};
console.log(person.greet());
Note: this depends on how a function is called.
5. Call, Apply, Bind
Methods to control the this context and arguments.
Example: Using bind.
const person = { name: "Alice" };
function greet() {
return `Hello, ${this.name}!`;
}
const boundGreet = greet.bind(person);
console.log(boundGreet());
Note: call and apply invoke immediately.
6. Event Loop
The event loop manages asynchronous tasks in JavaScript.
Example: Asynchronous execution.
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
Note: Microtasks (Promises) run before macrotasks (setTimeout).
7. Promises Chaining
Promise chaining handles sequential asynchronous operations.
Example: Chained promises.
new Promise(resolve => setTimeout(() => resolve(1), 1000))
.then(num => num * 2)
.then(num => console.log(num));
Note: Handle errors with catch.
8. Async/Await Patterns
Async/await simplifies promise-based code.
Example: Async function with error handling.
async function fetchData() {
try {
await new Promise(resolve => setTimeout(() => resolve("Data"), 1000));
return "Success";
} catch (error) {
return "Error";
}
}
fetchData().then(console.log);
Note: Always use try-catch with async/await.
9. Fetch API
Fetch API retrieves resources from the network.
Example: Fetching JSON (simulated).
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => console.log(data.title));
Note: Handle network errors with catch.
10. Classes
Classes provide a cleaner syntax for object-oriented programming.
Example: Basic class.
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
const bob = new Person("Bob");
console.log(bob.greet());
Note: Classes are syntactic sugar over prototypes.
11. Inheritance
Inheritance allows classes to extend others.
Example: Extending a class.
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}!`;
}
}
class Student extends Person {
study() {
return `${this.name} is studying`;
}
}
const alice = new Student("Alice");
console.log(alice.greet(), alice.study());
Note: Use super to call parent methods.
12. Getters/Setters
Getters and setters control property access.
Example: Getter and setter.
class Person {
#name;
set name(value) { this.#name = value; }
get name() { return this.#name; }
}
const person = new Person();
person.name = "Alice";
console.log(person.name);
Note: Use private fields (#) for encapsulation.
13. Proxy Objects
Proxies intercept and customize operations on objects.
Example: Proxy for validation.
const handler = {
get(target, prop) {
return target[prop] || "Not found";
}
};
const obj = new Proxy({}, handler);
console.log(obj.name);
Note: Proxies are powerful for meta-programming.
14. Generators
Generators yield values one at a time.
Example: Simple generator.
function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}
const gen = generateNumbers();
console.log(gen.next().value);
Note: Generators are useful for lazy evaluation.
15. Iterators
Iterators provide custom iteration behavior.
Example: Custom iterator.
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
}
};
console.log([...myIterable]);
Note: Use with for...of loops.
16. WeakMap
WeakMap stores key-value pairs with weak references to keys.
Example: Using WeakMap.
const wm = new WeakMap();
const obj = {};
wm.set(obj, "data");
console.log(wm.get(obj));
Note: Keys are garbage-collected when no longer referenced.
17. WeakSet
WeakSet stores unique objects with weak references.
Example: Using WeakSet.
const ws = new WeakSet();
const obj = {};
ws.add(obj);
console.log(ws.has(obj));
Note: Objects are garbage-collected when unreferenced.
18. Advanced Array Methods
Methods like flat and flatMap handle nested arrays.
Example: Flattening arrays.
const arr = [1, [2, [3]]];
console.log(arr.flat(2));
Note: Specify depth for flat.
19. Debouncing
Debouncing limits the rate of function execution.
Example: Debounce function.
function debounce(fn, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
const log = debounce(() => console.log("Debounced!"), 1000);
log();
Note: Useful for handling frequent events like scrolling.
20. Throttling
Throttling ensures a function runs at most once in a period.
Example: Throttle function.
function throttle(fn, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
const log = throttle(() => console.log("Throttled!"), 1000);
log();
Note: Ideal for rate-limiting events like resizing.
21. Custom Events
Custom events allow triggering and handling user-defined events.
Example: Dispatching a custom event.
const myEvent = new Event("myEvent");
document.addEventListener("myEvent", () => console.log("Custom event!"));
document.dispatchEvent(myEvent);
Note: Use CustomEvent for passing data.
22. Web Storage Events
Storage events detect changes to localStorage or sessionStorage.
Example: Listening for storage changes.
window.addEventListener("storage", (e) => {
console.log(`Key: ${e.key}, Value: ${e.newValue}`);
});
localStorage.setItem("test", "value");
Note: Events fire in other tabs/windows.
23. Event Bubbling/Capturing
Events propagate through the DOM in bubbling or capturing phases.
Example: Event bubbling.
document.getElementById("child").addEventListener("click", () => console.log("Child clicked"));
document.getElementById("parent").addEventListener("click", () => console.log("Parent clicked"));
Note: Use stopPropagation to prevent bubbling.
24. Mutation Observer
MutationObserver detects DOM changes.
Example: Observing DOM mutations.
const observer = new MutationObserver(() => console.log("DOM changed"));
observer.observe(document.getElementById("target"), { childList: true });
document.getElementById("target").appendChild(document.createElement("div"));
Note: Useful for dynamic content updates.
25. Intersection Observer
IntersectionObserver tracks element visibility in the viewport.
Example: Lazy loading.
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) console.log("Visible");
});
});
observer.observe(document.getElementById("target"));
Note: Ideal for lazy loading and animations.
26. Resize Observer
ResizeObserver detects element size changes.
Example: Observing resize.
const observer = new ResizeObserver(entries => {
console.log(entries[0].contentRect.width);
});
observer.observe(document.getElementById("target"));
Note: Use for responsive layouts.
27. Performance Timing
Performance API measures page load times.
Example: Measuring load time.
const { domContentLoadedEventEnd, navigationStart } = performance.timing;
console.log(domContentLoadedEventEnd - navigationStart);
Note: Use for performance optimization.
28. Web Workers
Web Workers run scripts in background threads.
Example: Worker (simulated).
const worker = new Worker(URL.createObjectURL(new Blob(["onmessage = e => postMessage(e.data * 2);"])));
worker.postMessage(5);
worker.onmessage = e => console.log(e.data);
Note: Workers don't access the DOM.
29. Service Workers
Service Workers enable offline capabilities and caching.
Example: Registering a service worker (simulated).
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js").then(() => console.log("Registered"));
}
Note: Requires HTTPS for production.
30. IndexedDB
IndexedDB stores large amounts of structured data.
Example: Basic IndexedDB (simulated).
const request = indexedDB.open("myDB");
request.onsuccess = () => console.log("Database opened");
Note: Use for client-side storage.
31. Dynamic Imports
Dynamic imports load modules on demand.
Example: Dynamic import (simulated).
import("./module.js").then(module => console.log(module.default()));
Note: Improves performance by lazy loading.
32. Module Patterns
Module patterns organize code with private and public members.
Example: Module pattern.
const myModule = (function() {
const privateVar = "secret";
return {
getSecret: () => privateVar
};
})();
console.log(myModule.getSecret());
Note: Encapsulates private data.
33. Memoization
Memoization caches function results for performance.
Example: Memoized factorial.
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const factorial = memoize(n => n === 0 ? 1 : n * factorial(n - 1));
console.log(factorial(5));
Note: Use for expensive computations.
34. Currying
Currying transforms a function to take arguments one at a time.
Example: Curried function.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn(...args);
return (...moreArgs) => curried(...args, ...moreArgs);
};
}
const add = curry((a, b) => a + b);
console.log(add(2)(3));
Note: Enables partial application.
35. Function Composition
Function composition combines functions to create new ones.
Example: Composing functions.
const compose = (f, g) => x => f(g(x));
const double = x => x * 2;
const square = x => x * x;
const doubleThenSquare = compose(square, double);
console.log(doubleThenSquare(3));
Note: Read from right to left.
36. Error Boundaries
Error boundaries catch errors in child components (simulated for vanilla JS).
Example: Error boundary.
function errorBoundary(fn) {
try {
fn();
} catch (error) {
console.log("Error caught:", error.message);
}
}
errorBoundary(() => { throw new Error("Test error"); });
Note: Common in frameworks like React.
37. WebSocket Basics
WebSockets enable real-time communication.
Example: WebSocket connection (simulated).
const ws = new WebSocket("ws://example.com");
ws.onopen = () => console.log("Connected");
ws.onmessage = e => console.log(e.data);
Note: Requires a WebSocket server.
38. Typed Arrays
Typed arrays handle binary data efficiently.
Example: Using Int32Array.
const arr = new Int32Array([1, 2, 3]);
console.log(arr[0]);
Note: Fixed-size and type-specific.
39. BigInt
BigInt handles large integers beyond Number limits.
Example: BigInt arithmetic.
const big = 12345678901234567890n;
console.log(big + 1n);
Note: Use n suffix for BigInt literals.
40. Intl API
Intl API formats numbers, dates, and strings for internationalization.
Example: Formatting currency.
const formatter = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
console.log(formatter.format(1234.56));
Note: Supports locale-specific formatting.