DEV Community

Cover image for πŸ§™β€β™‚οΈ JavaScript Decorators Explained – Like Magic, but Real!
YASH SAWARKAR
YASH SAWARKAR

Posted on

πŸ§™β€β™‚οΈ JavaScript Decorators Explained – Like Magic, but Real!

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 {}
Enter fullscreen mode Exit fullscreen mode

Or for methods:

class MyClass {
  @log
  doSomething() {}
}
Enter fullscreen mode Exit fullscreen mode

βš™οΈ 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;
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

🧰 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
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

πŸ”¬ 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
  }
}
Enter fullscreen mode Exit fullscreen mode

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! 🌟

Top comments (0)