JavaScript Class Decorators Explained
JavaScript class decorators are a powerful language feature that allows developers to customize, extend, and annotate classes and their members using a declarative syntax. This article explores what decorators are, why they are useful, and how the official TC39 Stage 3 standard proposal defines their structure, context object, and execution lifecycle in modern JavaScript.
What Are JavaScript Class Decorators?
A decorator is essentially a higher-order function applied to a class
or a class member (such as a method, field, getter, or setter) using the
@ prefix. Decorators allow you to wrap, modify, or observe
the behavior of the target element at definition time, promoting code
reusability and separating cross-cutting concerns like logging,
validation, memoization, and access control.
@logged
class Person {
@memoize
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}The TC39 Standard Proposal
Decorators have evolved significantly over the years. Early implementations, commonly known as “legacy” or “experimental” decorators in TypeScript and Babel (Stage 1/2), differed in their mechanics. The current standard is the TC39 Stage 3 Decorators proposal. This standard introduces a cleaner, safer, and more unified API designed to prevent unintended prototype mutations and provide explicit lifecycle hooks.
How the Standard Defines Decorators
Under the standard proposal, a decorator is a function invoked with two arguments:
value: The target being decorated (e.g., the class constructor, method function, orundefinedin the case of fields).context: An object containing metadata and helper utilities related to the target element.
The context Object
The context argument provides strict, read-only metadata
about the element being decorated:
kind: A string indicating the target type ("class","method","getter","setter","field", or"accessor").name: The name of the member (a string or symbol) orundefinedfor anonymous classes.static: A boolean indicating whether the member is static.private: A boolean indicating whether the member is private (#field).access: An object withgetandsetfunctions to read or write the member dynamically.addInitializer(callback): A method allowing the decorator to register an initialization hook that runs during instance creation or class evaluation.
Types of Decorators Defined by the Proposal
1. Class Decorators
A class decorator receives the class constructor as
value and
{ kind: "class", name, addInitializer } as
context. It can return a new constructor to replace the
original class, or return nothing to leave the class unchanged.
function sealed(targetClass, context) {
if (context.kind === "class") {
Object.seal(targetClass);
Object.seal(targetClass.prototype);
}
}
@sealed
class User {}2. Method Decorators
A method decorator receives the method function as
value. It can return a new function that wraps or replaces
the original method.
function logged(originalMethod, context) {
if (context.kind === "method") {
return function (...args) {
console.log(`Calling ${String(context.name)} with args:`, args);
return originalMethod.apply(this, args);
};
}
}3. Field Decorators
Field decorators receive undefined as value
because fields do not have an initial value at the time the class
definition is evaluated. A field decorator can return an initializer
function that modifies the initial value assigned to the field when an
instance is created.
function defaultValue(fallback) {
return function (value, context) {
if (context.kind === "field") {
return (initialValue) => initialValue ?? fallback;
}
};
}4. Auto-Accessor Decorators
The standard proposal introduces the accessor keyword
for class fields. Auto-accessors generate a private storage slot along
with automatic getter and setter functions. An accessor decorator
receives an object containing { get, set } and can return
an object replacing the getter, setter, or the initial value.
class State {
@tracked accessor count = 0;
}Execution Order
The TC39 proposal defines a strict execution order: 1. Decorator expressions are evaluated from top to bottom, outer to inner. 2. Decorators are applied to class elements from bottom to top, inner to outer. 3. Member decorators run first as the class body is parsed. 4. Class-level decorators run last once the entire class body has been defined.