40 Intermediate JavaScript Tutorials

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());
Output: Hello, Alice!

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());
Output: 1 1

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);
})();
Output: Hidden

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());
Output: Hello, Bob!

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());
Output: Hello, Alice!

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");
Output: Start End Promise Timeout

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());
Output: Hello, Bob!

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());
Output: Hello, Alice! Alice is studying

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);
Output: Alice

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);
Output: Not found

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);
Output: 1

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]);
Output: [1, 2]

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));
Output: data

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));
Output: true

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));
Output: [1, 2, 3]

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"));
Click Me

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"));
Scroll to see

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);
Output: ms

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"));
}
Output: Service worker registration (simulated)

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()));
Output: Module loaded (simulated)

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());
Output: secret

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));
Output: 120

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));
Output: 5

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));
Output: 36

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"); });
Output: Error caught: 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);
Output: Connected (simulated)

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]);
Output: 1

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);
Output: 12345678901234567891

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));
Output: $1,234.56

Note: Supports locale-specific formatting.

Macro Nepal Helper