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.