Skip to main content
Piotr Majkutewicz
Back to Blog

Decorators: Enhancing Class Features in JavaScript

Decorators provide a powerful way to modify or enhance classes and their members at design time. They allow you to add metadata, wrap methods, or completely transform how a class behaves.

Basic Decorator Syntax

Decorators use the @ symbol and can be applied to classes, methods, properties, and accessors:

typescript
function logged(target: any, context: ClassMethodDecoratorContext) {
  return function(...args: any[]) {
    console.log(`Calling ${context.name.toString()} with args: ${args}`);
    return target.call(this, ...args);
  }
}

class Calculator {
  @logged
  add(a: number, b: number) {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3); // Logs: "Calling add with args: 2,3"

Common Decorator Types

1. Class Decorators

typescript
function singleton<T extends new (...args: any[]) => any>(
  target: T,
  context: ClassDecoratorContext
) {
  let instance: any;
  
  return class extends target {
    constructor(...args: any[]) {
      if (instance) return instance;
      instance = new target(...args);
      return instance;
    }
  };
}

@singleton
class Database {
  constructor(url: string) {
    console.log(`Connecting to ${url}`);
  }
}

// Only logs once
const db1 = new Database('localhost');
const db2 = new Database('localhost'); // Returns existing instance

2. Method Decorators

typescript
function memoize(target: any, context: ClassMethodDecoratorContext) {
  const cache = new Map();
  
  return function(...args: any[]) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    
    const result = target.apply(this, args);
    cache.set(key, result);
    return result;
  }
}

class MathUtils {
  @memoize
  fibonacci(n: number): number {
    if (n <= 1) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }
}

3. Property Decorators

typescript
function validateRange(min: number, max: number) {
  return function(target: any, context: ClassFieldDecoratorContext) {
    return function(this: any, value: number) {
      if (value < min || value > max) {
        throw new Error(`Value must be between ${min} and ${max}`);
      }
      return value;
    }
  }
}

class Player {
  @validateRange(0, 100)
  health: number = 100;
}

Real-World Applications

Authentication Decorator

typescript
function requireAuth(target: any, context: ClassMethodDecoratorContext) {
  return function(this: any, ...args: any[]) {
    if (!this.isAuthenticated()) {
      throw new Error('Authentication required');
    }
    return target.apply(this, args);
  }
}

class UserAPI {
  private authenticated = false;

  isAuthenticated() {
    return this.authenticated;
  }

  @requireAuth
  getUserData() {
    return { name: 'John', email: '[email protected]' };
  }
}

Performance Monitoring

typescript
function measure(target: any, context: ClassMethodDecoratorContext) {
  return function(...args: any[]) {
    const start = performance.now();
    const result = target.apply(this, args);
    const end = performance.now();
    
    console.log(`${context.name.toString()} took ${end - start}ms`);
    return result;
  }
}

Best Practices

  • Keep decorators focused on a single responsibility
  • Use TypeScript for better type safety and IDE support
  • Consider performance implications of method wrapping
  • Document decorator behavior clearly
  • Test decorated classes thoroughly

Browser Support

Decorators are a relatively new feature. Use TypeScript or Babel for transpilation to ensure compatibility with older browsers. Check browser support for native implementation.