Decorators are a powerful way to enhance classes and their members (methods or properties) without touching their original code directly. 🎯
They let you decorate behaviour in a clean and reusable way. Like adding toppings to a pizza 🍕

⚠️ Decorators are a Stage-3 proposal in JavaScript. You can use them with TypeScript or Babel.


🔍 What is a Decorator?

A decorator is a special function you can attach to:

  • A class
  • A class method
  • A class property

It looks like this:

@myDecorator
class MyClass {}

Or for methods:

class MyClass {
  @log
  doSomething() {}
}

⚙️ How Do Decorators Work Behind the Scenes?

Think of a decorator as a function that wraps the original function, property, or class to change its behavior.

Here's what happens behind the scenes for method decorators:

  1. When the class is defined, decorators are executed.
  2. The decorator function receives three arguments:
  • target: The class prototype (for instance methods) or constructor (for static methods)
  • propertyKey: The name of the method/property being decorated
  • descriptor: The property descriptor that describes the method
    1. The decorator can change the method (e.g., add logging) by modifying the descriptor.

Behind-the-scenes sketch:

function log(target, propertyKey, descriptor) {
  // Modify the original function
  const originalMethod = descriptor.value;
  descriptor.value = function (...args) {
    console.log(`Calling ${propertyKey} with`, args);
    return originalMethod.apply(this, args);
  };
  return descriptor;
}

Decorators don’t execute at runtime like normal code — they execute when the class is first defined.


🧪 Basic Example – Logging Function Calls

Let’s see a working example:

function log(target, propertyName, descriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args) {
    console.log(`📞 ${propertyName} called with`, args);
    const result = original.apply(this, args);
    console.log(`✅ ${propertyName} returned`, result);
    return result;
  };

  return descriptor;
}

class Calculator {
  @log
  add(a, b) {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(5, 3);

🧰 Real-World Use Cases

1. ✅ Logging and Debugging

Great for tracing code behavior during development.

2. 🔐 Authorization Guards

function requireAdmin(target, key, descriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args) {
    if (!this.isAdmin) {
      throw new Error("⛔️ Access denied");
    }
    return original.apply(this, args);
  };
  return descriptor;
}

class Dashboard {
  constructor(isAdmin) {
    this.isAdmin = isAdmin;
  }

  @requireAdmin
  deleteUser() {
    console.log("🗑️ User deleted");
  }
}

const admin = new Dashboard(true);
admin.deleteUser(); // 🗑️ User deleted

const guest = new Dashboard(false);
guest.deleteUser(); // ⛔️ Error: Access denied

3. 🧹 Auto-Binding Methods

function autobind(target, key, descriptor) {
  const original = descriptor.value;
  return {
    configurable: true,
    enumerable: false,
    get() {
      return original.bind(this);
    },
  };
}

class Printer {
  message = "💨 Hello from Printer!";

  @autobind
  print() {
    console.log(this.message);
  }
}

const p = new Printer();
const printFn = p.print;
printFn(); // 💨 Hello from Printer!

🔬 Types of Decorators

JavaScript (and especially TypeScript) supports different types of decorators:

Type Target Purpose
Class decorator Whole class Modify or enhance a class
Method decorator Class method Wrap or replace a method
Property decorator Class property Add metadata or transformations
Parameter decorator Method param Metadata for method parameters

📊 Setup for TypeScript

Enable decorators in your tsconfig.json:

{
  "compilerOptions": {
    "target": "ES6",
    "experimentalDecorators": true
  }
}

For Babel, use: @babel/plugin-proposal-decorators


🧠 Summary Table

Feature Target Purpose
@log Method Logging/debugging
@autobind Method Fix this binding
@requireAdmin Method Authorization check

💭 Deeper Insight: Why Decorators?

Decorators provide a declarative way to apply cross-cutting concerns like:

  • ✅ Logging
  • 🔁 Memoization
  • 🔐 Access control
  • 💾 Caching
  • ⌚ Rate limiting

Rather than adding repetitive boilerplate in every method, you write it once in a decorator and reuse it across your codebase.

This makes your business logic clean, reusable, and easy to read 💡


💬 Final Thoughts

Decorators help keep your code clean, elegant, and modular.
They're like invisible helpers that wrap around your logic — magical, but under your control 💫

Use them wisely and your code will thank you!


📖 Resources


Happy Decorating! 🌟