Amblem
Furkan Baytekin

JavaScript Decorators: Native Support, TypeScript, and Beyond

Master JavaScript decorators to write cleaner, more powerful code

JavaScript Decorators: Native Support, TypeScript, and Beyond
105
9 minutes

JavaScript is a language that thrives on flexibility, and over the years, it has gained features that push its capabilities into exciting new territories. One such feature is decorators—a powerful tool for meta-programming that lets you modify or enhance the behavior of classes, methods, and properties in a declarative way. If you’ve worked with frameworks like Angular or explored Python’s decorators, you might already have a sense of their potential. But where do decorators stand in JavaScript today? Are they natively supported, or do you need a tool like TypeScript to use them? In this deep dive, we’ll answer these questions, explore how decorators work, and walk through practical examples to help you master this feature.

What Are Decorators?

A decorator is a special kind of function that “wraps” or modifies another function, method, property, or class. Think of it as a way to attach additional behavior or metadata to your code without cluttering its core logic. Decorators use the @ symbol followed by a function name, placed directly above the thing they’re decorating. They’re part of a broader programming concept called meta-programming, which involves writing code that manipulates other code.

Here’s a simple conceptual example (don’t worry if it doesn’t run yet—we’ll get to that):

javascript
@log class MyClass { sayHello() { console.log("Hello!"); } } function log(target) { console.log("Class created:", target); }

In this imaginary snippet, the @log decorator would log information about MyClass when it’s defined. Decorators can do much more, like altering methods, enforcing rules, or adding debugging capabilities.

Decorators were inspired by languages like Python and Java, but they’ve been adapted to fit JavaScript’s object-oriented 🤭 and functional nature. They’re particularly popular in frameworks that rely heavily on classes, such as Angular and NestJS.

The State of Decorators in JavaScript

Before we dive into examples, let’s address the big question: Does JavaScript support decorators natively? As of March 15, 2025 (the current date), the answer is not yet—but it’s getting closer.

Decorators are part of a proposal in the ECMAScript specification, currently at Stage 3 of the TC39 process. The TC39 committee oversees JavaScript’s evolution, and their process has four stages:

Stage 3 means decorators are well-defined, have experimental support in some environments, and are likely to be finalized soon—potentially in ECMAScript 2025 or 2026. However, as of now, vanilla JavaScript in browsers and Node.js does not support decorators natively. To use them, you need a transpiler like Babel or a language extension like TypeScript, both of which implement the decorator proposal ahead of native adoption.

Native Support: When Will It Happen?

While we can’t predict the exact timeline, the decorator proposal has matured significantly since its early days. The current version (sometimes called the “new decorators proposal”) is more robust and aligns better with JavaScript’s internals than earlier iterations. Once it reaches Stage 4, browsers and Node.js will begin implementing it natively. Until then, tools like Babel and TypeScript are your best bet.

Using Decorators with Babel

If you want to use decorators in plain JavaScript today, Babel is the go-to solution. Babel is a transpiler that converts modern JavaScript (including experimental features) into code that runs in current environments. Here’s how to set up and use decorators with Babel.

Setup

  1. Install Babel and the decorator plugin:
bash
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/plugin-proposal-decorators
  1. Configure Babel (e.g., in a .babelrc file):
json
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-proposal-decorators", { "version": "2023-11" }] ] }

Note: The "version": "2023-11" option uses the latest decorator proposal as of late 2023. Check Babel’s documentation for updates, as the syntax evolves.

  1. Write and transpile your code.

Example: Method Decorator

Let’s create a decorator that logs method calls:

javascript
function logMethod(target, property, descriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args) { console.log(`Calling ${property} with args: ${args}`); return originalMethod.apply(this, args); }; return descriptor; } class Greeter { @logMethod sayHello(name) { return `Hello, ${name}!`; } } const greeter = new Greeter(); console.log(greeter.sayHello("Alice")); // "Calling sayHello with args: Alice" // "Hello, Alice!"

Here, @logMethod wraps the sayHello method, logging its arguments before calling the original implementation. The target is the class prototype, property is the method name, and descriptor is the property descriptor (like those used in Object.defineProperty).

Using Decorators with TypeScript

TypeScript offers built-in support for decorators, making it a popular choice for developers who want to use them without waiting for native JavaScript support. TypeScript has supported an earlier version of decorators since version 1.5 (2015), and it now aligns with the modern proposal (with some configuration).

Setup

  1. Enable decorators in your tsconfig.json:

    json
    { "compilerOptions": { "target": "ES5", "experimentalDecorators": true, "emitDecoratorMetadata": true } }
    • experimentalDecorators: Enables decorator support (based on an older proposal by default).
    • emitDecoratorMetadata: Optional, adds metadata for frameworks like Angular.
  2. Write your TypeScript code and compile it to JavaScript.

Example: Class Decorator

Here’s a TypeScript example that seals a class to prevent modifications:

typescript
function sealed(target: Function) { Object.seal(target); Object.seal(target.prototype); } @sealed class Person { name: string = "Bob"; sayHi() { return "Hi!"; } } const person = new Person(); console.log(person.name); // "Bob" // Attempting to add properties will fail silently (or throw in strict mode) person.age = 30; console.log(person.age); // undefined

The @sealed decorator uses Object.seal() to lock down the class and its prototype, preventing new properties or methods from being added.

Native JavaScript vs. TypeScript: Key Differences

So, do you need TypeScript for decorators? Not necessarily—it depends on your project:

My advice: Learn TypeScript and say goodbye to the pain of using type-less languages. Thank me later.

The modern decorator proposal (used by Babel’s latest plugin) differs from TypeScript’s default implementation. For example, TypeScript’s decorators don’t natively support the new “decorator context” object introduced in the 2023 proposal. However, TypeScript is working to adopt the latest standard, and you can use experimental flags or plugins to bridge the gap.

Practical Use Cases

Let’s explore some real-world examples to see decorators in action. These will work with either Babel or TypeScript (with minor adjustments).

1. Logging Decorator

We’ve seen a basic logging decorator, but let’s enhance it:

javascript
function logDetailed(target, property, descriptor) { const original = descriptor.value; descriptor.value = function (...args) { const start = performance.now(); const result = original.apply(this, args); const end = performance.now(); console.log(`${property} took ${end - start}ms, args: ${args}, result: ${result}`); return result; }; return descriptor; } class Calculator { @logDetailed add(a, b) { return a + b; } } const calc = new Calculator(); calc.add(5, 3); // Logs something like: "add took 0.012ms, args: 5,3, result: 8"

This logs execution time, arguments, and return values—great for debugging or performance profiling.

2. Validation Decorator

Ensure method arguments meet certain criteria:

javascript
function validatePositive(target, property, descriptor) { const original = descriptor.value; descriptor.value = function (...args) { if (args.some(arg => typeof arg !== "number" || arg < 0)) { throw new Error(`${property}: All arguments must be positive numbers`); } return original.apply(this, args); }; return descriptor; } class MathOps { @validatePositive multiply(a, b) { return a * b; } } const math = new MathOps(); console.log(math.multiply(2, 3)); // 6 math.multiply(-1, 5); // Throws: "multiply: All arguments must be positive numbers"

This enforces rules declaratively, keeping the core logic clean.

3. Read-Only Property Decorator

Make properties immutable:

javascript
function readonly(target, property, descriptor) { descriptor.writable = false; return descriptor; } class Config { @readonly version = "1.0.0"; } const config = new Config(); console.log(config.version); // "1.0.0" config.version = "2.0.0"; // Fails silently (or throws in strict mode)

This is simpler than Object.defineProperty and more readable.

4. Class Decorator for Dependency Injection

Simulate a basic dependency injection system:

javascript
const services = new Map(); function injectable(target) { return function (...args) { const instance = new target(...args); services.set(target.name, instance); return instance; }; } @injectable class Logger { log(message) { console.log(`Log: ${message}`); } } const logger = new Logger(); services.get("Logger").log("Hello!"); // "Log: Hello!"

This stores class instances in a service registry, a pattern common in frameworks.

Advantages and Limitations

Advantages

Limitations

Best Practices

  1. Keep It Simple: Start with basic decorators (e.g., logging) before tackling complex ones.
  2. Test Thoroughly: Decorators can introduce subtle bugs, especially with this binding or descriptors.
  3. Stay Updated: Monitor the TC39 proposal for changes if you’re using Babel.
  4. Use TypeScript for Types: If you’re in TypeScript, leverage its type system to make decorators safer.

Conclusion

JavaScript decorators are a fascinating feature that bring declarative meta-programming to the language. While they’re not yet natively supported as of March 15, 2025, tools like Babel and TypeScript make them accessible today. Babel aligns with the cutting-edge TC39 proposal, offering a glimpse of JavaScript’s future, while TypeScript provides a stable, widely-adopted alternative with type safety. Whether you’re logging method calls, enforcing rules, or building framework-like functionality, decorators can streamline your code and unlock new possibilities.

Experiment with the examples above—set up a small project with Babel or TypeScript, and see how decorators fit into your workflow. As the proposal moves to Stage 4, native support will make them even more compelling. Until then, they’re a powerful tool in your JavaScript toolkit, ready to enhance your next project.

Album of the day:

Suggested Blog Posts