-
-
Notifications
You must be signed in to change notification settings - Fork 2
JavaScript Advanced Guide
- Introduction
- Object-Oriented Programming (OOP)
- Prototype System
- Closures and Scope
- The this Keyword Deep Dive
- Asynchronous Patterns
- Design Patterns
- Performance Optimization
- Memory Management
- Advanced Array Operations
- Symbol and Iteration
- Reflect API
- Proxy API
This guide covers advanced JavaScript concepts including object-oriented programming, the prototype system, closures, the event loop, design patterns, and performance optimization. These concepts will help you write more sophisticated and efficient code.
Classes in JavaScript provide a cleaner syntax for creating objects and implementing inheritance:
// Class declaration
class Person {
// Constructor - called when creating new instance
constructor(name, age) {
this.name = name;
this.age = age;
}
// Method
greet() {
return `Hello, my name is ${this.name}`;
}
// Another method
celebrateBirthday() {
this.age++;
return `Happy birthday! Now I am ${this.age}`;
}
}
// Creating an instance
const alice = new Person("Alice", 25);
console.log(alice.greet()); // "Hello, my name is Alice"
console.log(alice.celebrateBirthday()); // "Happy birthday! Now I am 26"
// Class expression
const Animal = class {
constructor(species) {
this.species = species;
}
speak() {
return `${this.species} makes a sound`;
}
};Use extends to create a class that inherits from another:
// Parent class
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
move() {
return `${this.name} moves`;
}
}
// Child class
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
// Override parent method
speak() {
return `${this.name} barks`;
}
// New method specific to Dog
fetch() {
return `${this.name} fetches the ball`;
}
}
const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.speak()); // "Buddy barks"
console.log(dog.move()); // "Buddy moves" (inherited)
console.log(dog.fetch()); // "Buddy fetches the ball"
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // truePrivate fields (prefixed with #) encapsulate data:
class BankAccount {
// Private fields
#balance;
#transactions = [];
constructor(initialBalance = 0) {
this.#balance = initialBalance;
}
// Public method to get balance
getBalance() {
return this.#balance;
}
// Deposit money
deposit(amount) {
if (amount <= 0) {
throw new Error("Deposit amount must be positive");
}
this.#balance += amount;
this.#transactions.push({ type: 'deposit', amount });
return this.#balance;
}
// Withdraw money
withdraw(amount) {
if (amount <= 0) {
throw new Error("Withdrawal amount must be positive");
}
if (amount > this.#balance) {
throw new Error("Insufficient funds");
}
this.#balance -= amount;
this.#transactions.push({ type: 'withdraw', amount });
return this.#balance;
}
// Private method
#logTransaction(type, amount) {
console.log(`${type}: $${amount}`);
}
}
const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000
account.deposit(500); // 1500
// console.log(account.#balance); // SyntaxError - can't access private field
// console.log(account.#transactions); // SyntaxErrorStatic members belong to the class itself, not instances:
class MathUtils {
static PI = 3.14159;
static add(a, b) {
return a + b;
}
static factorial(n) {
if (n <= 1) return 1;
return n * this.factorial(n - 1);
}
// Static factory method
static fromDegrees(degrees) {
return degrees * (MathUtils.PI / 180);
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.factorial(5)); // 120
console.log(MathUtils.fromDegrees(90)); // ~1.57
// Cannot call static on instance
const utils = new MathUtils();
// utils.add(2, 3); // TypeErrorGetters and setters provide controlled access to properties:
class Temperature {
constructor(celsius) {
this.celsius = celsius;
}
// Getter
get fahrenheit() {
return (this.celsius * 9/5) + 32;
}
// Setter
set fahrenheit(value) {
this.celsius = (value - 32) * 5/9;
}
get kelvin() {
return this.celsius + 273.15;
}
set kelvin(value) {
this.celsius = value - 273.15;
}
}
const temp = new Temperature(25);
console.log(temp.celsius); // 25
console.log(temp.fahrenheit); // 77
console.log(temp.kelvin); // 298.15
temp.fahrenheit = 100;
console.log(temp.celsius); // ~37.78Every JavaScript object has a prototype, which is another object from which it inherits properties.
// Every object has a prototype
const obj = {};
console.log(obj.__proto__); // Object.prototype
console.log(obj.__proto__.__proto__); // null
// Arrays inherit from Array.prototype
const arr = [1, 2, 3];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
// Functions inherit from Function.prototype
function foo() {}
console.log(foo.__proto__ === Function.prototype); // true// Create object with specific prototype
const animal = {
speak() {
return `${this.name} makes a sound`;
},
move() {
return `${this.name} moves`;
}
};
const dog = Object.create(animal);
dog.name = "Buddy";
dog.breed = "Golden Retriever";
console.log(dog.speak()); // "Buddy makes a sound"
console.log(dog.move()); // "Buddy moves"
console.log(dog.hasOwnProperty('name')); // true
console.log(dog.hasOwnProperty('speak')); // false (inherited)
console.log(animal.isPrototypeOf(dog)); // true// Creating inheritance chain
const Mammal = {
isWarmBlooded: true,
giveBirth() {
return `${this.name} gives live birth`;
}
};
const Primate = Object.create(Mammal);
Primate.hasOpposableThumbs = true;
Primate.useTools = function() {
return `${this.name} uses tools`;
};
const Human = Object.create(Primate);
Human.name = "Alice";
Human.speak = function() {
return `${this.name} speaks`;
};
console.log(Human.isWarmBlooded); // true (inherited from Mammal)
console.log(Human.hasOpposableThumbs); // true (inherited from Primate)
console.log(Human.useTools()); // "Alice uses tools"
console.log(Human.speak()); // "Alice speaks"
console.log(Human.giveBirth()); // "Alice gives live birth"A closure is a function that remembers its outer variables even after the outer function has returned:
// Simple closure example
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// Each call creates a new closure
const counter2 = createCounter();
console.log(counter2()); // 1 (separate count)
// Closure with parameters
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15// Private variables via closure
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
if (amount > 0) {
balance += amount;
return balance;
}
throw new Error("Invalid amount");
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return balance;
}
throw new Error("Invalid amount or insufficient funds");
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount(100);
console.log(account.getBalance()); // 100
account.deposit(50);
console.log(account.getBalance()); // 150
account.withdraw(75);
console.log(account.getBalance()); // 75
// Event handler with closure
function setupButtonHandler(buttonId, count) {
const button = document.getElementById(buttonId);
let clickCount = count || 0;
button.addEventListener('click', function() {
clickCount++;
console.log(`Button clicked ${clickCount} times`);
});
}
// Function factory
function createGame(name) {
let score = 0;
return {
name,
increaseScore(points) {
score += points;
console.log(`${name}: ${score}`);
},
reset() {
score = 0;
}
};
}
const game1 = createGame("Game 1");
const game2 = createGame("Game 2");
game1.increaseScore(10); // "Game 1: 10"
game2.increaseScore(20); // "Game 2: 20"// Potential memory leak - closure holding reference
function leakMemory() {
const largeData = new Array(1000000).fill("data");
return function() {
return largeData[0]; // Keeps largeData in memory
};
}
// Better approach - avoid holding unnecessary references
function noLeakMemory() {
const largeData = new Array(1000000).fill("data");
return function() {
return largeData[0]; // Necessary reference
};
// largeData becomes eligible for GC after function returns
}// In methods, 'this' refers to the object
const person = {
name: "Alice",
greet() {
return `Hello, I'm ${this.name}`;
}
};
console.log(person.greet()); // "Hello, I'm Alice"
// When extracted, 'this' loses context
const greetFn = person.greet;
console.log(greetFn()); // "Hello, I'm undefined" (or error in strict mode)
// Solution 1: Bind
const boundGreet = person.greet.bind(person);
console.log(boundGreet()); // "Hello, I'm Alice"
// Solution 2: Arrow function in object
const person2 = {
name: "Bob",
greet: () => {
return `Hello, I'm ${this.name}`; // 'this' is lexically bound
}
};function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const person = { name: "Alice" };
// call - invoke with specific 'this' and arguments
console.log(greet.call(person, "Hello", "!")); // "Hello, Alice!"
// apply - invoke with specific 'this' and array of arguments
console.log(greet.apply(person, ["Hi", "."])); // "Hi, Alice."
// bind - create new function with bound 'this'
const boundGreet = greet.bind(person);
console.log(boundGreet("Howdy", "?")); // "Howdy, Alice?"
// Partial application with bind
const greetAlice = greet.bind(person, "Hello");
console.log(greetAlice("!")); // "Hello, Alice!"// JavaScript is single-threaded but handles async via event loop
console.log("1. Start");
setTimeout(() => {
console.log("2. Timeout callback");
}, 0);
Promise.resolve().then(() => {
console.log("3. Promise resolved");
});
console.log("4. End");
// Output order:
// 1. Start
// 4. End
// 3. Promise resolved (microtask)
// 2. Timeout callback (macrotask)// Microtasks (Promises, queueMicrotask)
Promise.resolve().then(() => console.log("Promise 1"));
queueMicrotask(() => console.log("Microtask"));
// Macrotasks (setTimeout, setInterval, I/O)
setTimeout(() => console.log("Timeout"), 0);
// Execution order
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise 1"))
.then(() => Promise.resolve().then(() => console.log("Promise 2")));
queueMicrotask(() => console.log("Microtask"));
console.log("End");
// Output:
// Start
// End
// Promise 1
// Promise 2
// Microtask
// Timeout// Promise timeout
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), ms);
});
return Promise.race([promise, timeout]);
}
// Retry with exponential backoff
async function retry(fn, maxAttempts = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) throw error;
await new Promise(r => setTimeout(r, delay * attempt));
}
}
}
// Promise queue - run promises sequentially
async function runSequentially(promises) {
const results = [];
for (const promise of promises) {
results.push(await promise);
}
return results;
}
// Promise cache
const promiseCache = new Map();
async function fetchWithCache(url) {
if (promiseCache.has(url)) {
return promiseCache.get(url);
}
const promise = fetch(url).then(r => r.json());
promiseCache.set(url, promise);
return promise;
}// Using IIFE to create private scope
const CounterModule = (function() {
// Private state
let count = 0;
// Private function
function validate(value) {
return typeof value === 'number' && !isNaN(value);
}
// Public API
return {
increment(value = 1) {
if (validate(value)) {
count += value;
return count;
}
return count;
},
decrement(value = 1) {
if (validate(value)) {
count -= value;
return count;
}
return count;
},
getCount() {
return count;
},
reset() {
count = 0;
return count;
}
};
})();
console.log(CounterModule.increment()); // 1
console.log(CounterModule.increment(5)); // 6
console.log(CounterModule.getCount()); // 6
console.log(CounterModule.count); // undefined (private)// Singleton using class
class Singleton {
static #instance = null;
constructor() {
if (Singleton.#instance) {
return Singleton.#instance;
}
Singleton.#instance = this;
this.data = {};
}
set(key, value) {
this.data[key] = value;
}
get(key) {
return this.data[key];
}
}
const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true
// Singleton using IIFE
const Database = (function() {
let instance = null;
function createInstance() {
return {
query(sql) {
console.log(`Executing: ${sql}`);
}
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true// Factory function
function createUser(type, data) {
const baseUser = {
created: new Date(),
greet() {
return `Hello, I'm ${this.name}`;
}
};
switch (type) {
case 'admin':
return { ...baseUser, ...data, permissions: ['read', 'write', 'delete'] };
case 'moderator':
return { ...baseUser, ...data, permissions: ['read', 'write'] };
case 'user':
return { ...baseUser, ...data, permissions: ['read'] };
default:
throw new Error(`Unknown user type: ${type}`);
}
}
const admin = createUser('admin', { name: 'Alice', email: 'alice@example.com' });
console.log(admin.permissions); // ['read', 'write', 'delete']
// Factory using classes
class UserFactory {
static create(type, data) {
const baseProps = {
id: Date.now(),
createdAt: new Date(),
type
};
const typeSpecific = this.getTypeSpecificProps(type);
return { ...baseProps, ...typeSpecific, ...data };
}
static getTypeSpecificProps(type) {
const types = {
premium: { isPremium: true, maxStorage: '100GB' },
free: { isPremium: false, maxStorage: '5GB' }
};
return types[type] || {};
}
}// Simple Event Emitter
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(...args));
}
}
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
// Usage
const emitter = new EventEmitter();
emitter.on('message', (data) => {
console.log('Listener 1:', data);
});
emitter.on('message', (data) => {
console.log('Listener 2:', data.toUpperCase());
});
emitter.emit('message', 'Hello World');
// Listener 1: Hello World
// Listener 2: HELLO WORLD// Middleware pattern for request processing
class MiddlewareChain {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
async execute(context) {
let index = 0;
const next = async () => {
if (index >= this.middlewares.length) {
return context;
}
const middleware = this.middlewares[index++];
await middleware(context, next);
};
await next();
return context;
}
}
// Example middlewares
const logging = async (ctx, next) => {
console.log(`[${new Date().toISOString()}] ${ctx.method} ${ctx.url}`);
await next();
console.log('Response status:', ctx.status);
};
const auth = async (ctx, next) => {
if (!ctx.user) {
ctx.status = 401;
return;
}
await next();
};
const validate = async (ctx, next) => {
if (!ctx.body || Object.keys(ctx.body).length === 0) {
ctx.status = 400;
ctx.error = 'Request body required';
return;
}
await next();
};
// Usage
const chain = new MiddlewareChain();
chain.use(logging);
chain.use(auth);
chain.use(validate);
await chain.execute({ method: 'POST', url: '/api/users', user: { id: 1 }, body: { name: 'Alice' } });// Debounce - wait for function to stop being called
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
// Example - search input
const handleSearch = debounce((query) => {
console.log('Searching for:', query);
}, 300);
// User types "hello"
// After 300ms of not typing, logs "Searching for: hello"
// Throttle - limit function execution rate
function throttle(fn, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Example - scroll handler
const handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', handleScroll);// Simple memoization
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.apply(this, args);
cache.set(key, result);
return result;
};
}
// Example - expensive calculation
const fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(40)); // Fast due to memoization
console.log(fibonacci(40)); // Even faster from cache// Lazy load images
function lazyLoadImages() {
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
});
images.forEach(img => observer.observe(img));
}
// Dynamic import
async function loadFeature() {
if (needsPremiumFeature()) {
const { premiumFeature } = await import('./premium.js');
premiumFeature();
}
}JavaScript automatically cleans up unused memory through garbage collection:
// Objects become eligible for GC when no references exist
let obj = { name: "Alice" };
obj = null; // Previous object is now eligible for GC
// Closure can prevent GC
function createLeak() {
const largeData = new Array(10000000);
return function() {
return largeData.length;
};
}
const leak = createLeak(); // largeData stays in memory
leak = null; // Now eligible for GC// Common memory leak - forgotten timers
let data = [];
const interval = setInterval(() => {
data.push(new Array(1000000));
}, 1000);
// Clear to prevent leak
clearInterval(interval);
// Common leak - detached DOM nodes
const container = document.getElementById('container');
const button = document.createElement('button');
container.appendChild(button);
container.removeChild(button);
// button still in memory if referenced
// Common leak - global event listeners
class Handler {
constructor() {
this.data = new Array(1000000);
}
handleClick() {
console.log('Clicked');
}
}
const handler = new Handler();
document.addEventListener('click', handler.handleClick);
// Remove to prevent leak
document.removeEventListener('click', handler.handleClick);// Int8Array - signed 8-bit integers
const int8 = new Int8Array([1, 2, 127, -128]);
console.log(int8[0]); // 1
console.log(int8[3]); // -128
// Uint8Array - unsigned 8-bit integers
const uint8 = new Uint8Array([255, 256]); // 256 becomes 0
console.log(uint8[0]); // 255
console.log(uint8[1]); // 0
// Float32Array - 32-bit floating point
const float32 = new Float32Array([1.5, 2.5]);
console.log(float32[0]); // 1.5
// Int16Array, Int32Array, Uint16Array, Uint32Array, etc.
const int16 = new Int16Array(100); // Create array of 100 16-bit integers
// Typed arrays have fixed length
const arr = new Int8Array(3);
arr[0] = 100;
arr[1] = 200;
// arr[2] is 0 by default
// arr[3] would be out of bounds// Create buffer
const buffer = new ArrayBuffer(16);
console.log(buffer.byteLength); // 16
// View the buffer
const view = new DataView(buffer);
// Write values
view.setInt8(0, 127);
view.setInt16(2, 1000, true); // little-endian
view.setFloat64(4, 3.14159);
// Read values
console.log(view.getInt8(0)); // 127
console.log(view.getInt16(2, true)); // 1000
console.log(view.getFloat64(4)); // 3.14159// Create unique symbol
const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 === sym2); // false
// Well-known symbols
// Symbol.iterator - custom iteration
// Symbol.toStringTag - custom toString
// Symbol.hasInstance - custom instanceof
// Custom iterator
const collection = {
items: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.items.length) {
return { value: this.items[index++], done: false };
}
return { done: true };
}
};
}
};
for (const item of collection) {
console.log(item); // 1, 2, 3
}// Iterator protocol
const stringIterator = "hello"[Symbol.iterator]();
console.log(stringIterator.next()); // { value: 'h', done: false }
console.log(stringIterator.next()); // { value: 'e', done: false }
// Generator functions
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// Generator with parameters
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3// Reflect - built-in object for metaprogramming
// Create object with prototype
const proto = { greet() { return "Hello"; } };
const obj = Reflect.create(proto);
console.log(obj.greet()); // "Hello"
// Check if property exists
console.log(Reflect.has({ a: 1 }, 'a')); // true
console.log(Reflect.has({ a: 1 }, 'b')); // false
// Get/set properties
const target = { a: 1 };
Reflect.set(target, 'b', 2);
console.log(Reflect.get(target, 'b')); // 2
// Delete property
Reflect.deleteProperty({ a: 1 }, 'a');
console.log(Reflect.has({ a: 1 }, 'a')); // false
// Apply function
function add(a, b) { return a + b; }
console.log(Reflect.apply(add, null, [2, 3])); // 5// Proxy - intercept and customize operations on objects
const handler = {
get(target, property) {
console.log(`Getting ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Setting ${property} to ${value}`);
target[property] = value;
return true;
}
};
const proxy = new Proxy({}, handler);
proxy.name = "Alice"; // Setting name to Alice
console.log(proxy.name); // Getting name - Alice
// Validation with Proxy
const validator = {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number' || value < 0 || value > 150) {
throw new RangeError('Invalid age');
}
}
target[property] = value;
return true;
}
};
const person = new Proxy({}, validator);
person.age = 25; // OK
// person.age = -5; // RangeError| Topic | Key Concepts |
|---|---|
| OOP | Classes, inheritance, private fields, static methods |
| Prototypes | Prototype chain, Object.create, prototypal inheritance |
| Closures | Function scope, private variables, memory considerations |
| this | Binding, call/apply/bind |
| Async | Event loop, microtasks, macrotasks |
| Patterns | Module, Singleton, Factory, Observer, Middleware |
| Performance | Debouncing, throttling, memoization |
| Memory | Garbage collection, memory leaks |
| Advanced Arrays | Typed arrays, ArrayBuffer |
| Metaprogramming | Symbol, Reflect, Proxy, Generators |