Skip to content

JavaScript 装饰器模式详解

装饰器(Decorator)是一种强大的设计模式,允许我们在不修改原有代码的情况下,为对象添加新的功能。随着ES2024标准的推进,JavaScript原生装饰器即将到来,本文将全面解析装饰器的原理与实践。

装饰器模式基础

核心概念

装饰器模式的核心思想是组合优于继承,通过包装对象来扩展功能,而不是通过继承来实现。

javascript
// 传统继承方式的问题
class BasicCoffee {
  cost() {
    return 10;
  }
  
  description() {
    return "Basic Coffee";
  }
}

class MilkCoffee extends BasicCoffee {
  cost() {
    return super.cost() + 2;
  }
  
  description() {
    return super.description() + ", Milk";
  }
}

class SugarMilkCoffee extends MilkCoffee {
  cost() {
    return super.cost() + 1;
  }
  
  description() {
    return super.description() + ", Sugar";
  }
}

// 问题:需要为每种组合创建新类,类爆炸问题

装饰器模式解决方案

javascript
// 装饰器模式实现
class Coffee {
  constructor() {
    this.cost = 10;
    this.description = "Basic Coffee";
  }
}

// 装饰器基类
class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  
  getCost() {
    return this.coffee.cost;
  }
  
  getDescription() {
    return this.coffee.description;
  }
}

// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
  constructor(coffee) {
    super(coffee);
  }
  
  getCost() {
    return this.coffee.getCost() + 2;
  }
  
  getDescription() {
    return this.coffee.getDescription() + ", Milk";
  }
}

class SugarDecorator extends CoffeeDecorator {
  constructor(coffee) {
    super(coffee);
  }
  
  getCost() {
    return this.coffee.getCost() + 1;
  }
  
  getDescription() {
    return this.coffee.getDescription() + ", Sugar";
  }
}

// 使用装饰器
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);

console.log(coffee.getDescription()); // "Basic Coffee, Milk, Sugar"
console.log(coffee.getCost()); // 13

JavaScript装饰器语法

当前状态(Stage 3提案)

javascript
// 类装饰器
@logged
@sealed
class User {
  @readonly
  @validate
  name = '';
  
  @debounce(300)
  @authorize('admin')
  async updateProfile(data) {
    // 更新用户资料
  }
  
  @memoize
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

// 装饰器实现
function logged(target, context) {
  if (context.kind === 'class') {
    return class extends target {
      constructor(...args) {
        console.log(`Creating instance of ${target.name}`);
        super(...args);
      }
    };
  }
}

function readonly(target, context) {
  if (context.kind === 'field') {
    return function(initialValue) {
      return {
        get() {
          return initialValue;
        },
        set() {
          throw new Error('Cannot modify readonly property');
        }
      };
    };
  }
}

TypeScript装饰器(实验性)

typescript
// 启用装饰器支持
// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

// 类装饰器
function Component(config: ComponentConfig) {
  return function<T extends new(...args: any[]) => {}>(constructor: T) {
    return class extends constructor {
      componentConfig = config;
      
      render() {
        console.log(`Rendering ${config.name}`);
        return super.render?.() || null;
      }
    };
  };
}

// 方法装饰器
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with args:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`${propertyKey} returned:`, result);
    return result;
  };
  
  return descriptor;
}

// 属性装饰器
function MinLength(length: number) {
  return function(target: any, propertyKey: string) {
    let value: string;
    
    const getter = () => value;
    const setter = (newValue: string) => {
      if (newValue.length < length) {
        throw new Error(`${propertyKey} must be at least ${length} characters`);
      }
      value = newValue;
    };
    
    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  };
}

// 参数装饰器
function Required(target: any, propertyKey: string, parameterIndex: number) {
  const existingRequiredParameters: number[] = 
    Reflect.getOwnMetadata('required', target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata('required', existingRequiredParameters, target, propertyKey);
}

// 使用示例
@Component({ name: 'UserCard', version: '1.0' })
class UserCard {
  @MinLength(3)
  username: string = '';
  
  @Log
  updateUser(@Required name: string, age?: number) {
    this.username = name;
    return { name, age };
  }
}

实用装饰器实现

1. 性能监控装饰器

javascript
// 性能监控装饰器
function performance(target, context) {
  if (context.kind === 'method') {
    return function(...args) {
      const start = performance.now();
      const result = target.apply(this, args);
      
      if (result instanceof Promise) {
        return result.finally(() => {
          const end = performance.now();
          console.log(`${context.name} took ${end - start}ms`);
        });
      } else {
        const end = performance.now();
        console.log(`${context.name} took ${end - start}ms`);
        return result;
      }
    };
  }
}

// 使用示例
class DataService {
  @performance
  async fetchUsers() {
    const response = await fetch('/api/users');
    return response.json();
  }
  
  @performance
  processData(data) {
    return data.map(item => ({
      ...item,
      processed: true
    }));
  }
}

2. 缓存装饰器

javascript
// 缓存装饰器
function memoize(ttl = 60000) { // 默认缓存1分钟
  const cache = new Map();
  
  return function(target, context) {
    if (context.kind === 'method') {
      return function(...args) {
        const key = JSON.stringify(args);
        const cached = cache.get(key);
        
        if (cached && Date.now() - cached.timestamp < ttl) {
          console.log('Cache hit for', context.name);
          return cached.value;
        }
        
        const result = target.apply(this, args);
        
        if (result instanceof Promise) {
          return result.then(value => {
            cache.set(key, { value, timestamp: Date.now() });
            return value;
          });
        } else {
          cache.set(key, { value: result, timestamp: Date.now() });
          return result;
        }
      };
    }
  };
}

// 使用示例
class ApiService {
  @memoize(30000) // 缓存30秒
  async getUser(id) {
    console.log('Fetching user from API');
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  }
  
  @memoize(60000) // 缓存1分钟
  calculateExpensiveOperation(data) {
    console.log('Performing expensive calculation');
    return data.reduce((sum, item) => sum + item.value, 0);
  }
}

3. 防抖和节流装饰器

javascript
// 防抖装饰器
function debounce(delay) {
  let timeoutId;
  
  return function(target, context) {
    if (context.kind === 'method') {
      return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
          target.apply(this, args);
        }, delay);
      };
    }
  };
}

// 节流装饰器
function throttle(delay) {
  let lastCall = 0;
  
  return function(target, context) {
    if (context.kind === 'method') {
      return function(...args) {
        const now = Date.now();
        if (now - lastCall >= delay) {
          lastCall = now;
          return target.apply(this, args);
        }
      };
    }
  };
}

// 使用示例
class SearchComponent {
  @debounce(300)
  onSearchInput(query) {
    console.log('Searching for:', query);
    this.performSearch(query);
  }
  
  @throttle(1000)
  onScroll(event) {
    console.log('Scroll event handled');
    this.updateScrollPosition(event.target.scrollTop);
  }
}

4. 权限验证装饰器

javascript
// 权限验证装饰器
function authorize(requiredRole) {
  return function(target, context) {
    if (context.kind === 'method') {
      return function(...args) {
        const currentUser = this.getCurrentUser?.() || window.currentUser;
        
        if (!currentUser) {
          throw new Error('User not authenticated');
        }
        
        if (!currentUser.roles.includes(requiredRole)) {
          throw new Error(`Access denied. Required role: ${requiredRole}`);
        }
        
        return target.apply(this, args);
      };
    }
  };
}

// 审计日志装饰器
function audit(action) {
  return function(target, context) {
    if (context.kind === 'method') {
      return function(...args) {
        const user = this.getCurrentUser?.() || window.currentUser;
        const timestamp = new Date().toISOString();
        
        console.log(`Audit: ${user?.id} performed ${action} at ${timestamp}`);
        
        // 发送审计日志到服务器
        this.sendAuditLog?.({
          userId: user?.id,
          action,
          timestamp,
          args: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg)
        });
        
        return target.apply(this, args);
      };
    }
  };
}

// 使用示例
class AdminService {
  @authorize('admin')
  @audit('DELETE_USER')
  async deleteUser(userId) {
    console.log(`Deleting user ${userId}`);
    await fetch(`/api/users/${userId}`, { method: 'DELETE' });
  }
  
  @authorize('moderator')
  @audit('UPDATE_USER_ROLE')
  async updateUserRole(userId, newRole) {
    console.log(`Updating user ${userId} role to ${newRole}`);
    await fetch(`/api/users/${userId}/role`, {
      method: 'PUT',
      body: JSON.stringify({ role: newRole })
    });
  }
}

5. 重试装饰器

javascript
// 重试装饰器
function retry(maxAttempts = 3, delay = 1000) {
  return function(target, context) {
    if (context.kind === 'method') {
      return async function(...args) {
        let lastError;
        
        for (let attempt = 1; attempt <= maxAttempts; attempt++) {
          try {
            return await target.apply(this, args);
          } catch (error) {
            lastError = error;
            console.log(`Attempt ${attempt} failed:`, error.message);
            
            if (attempt < maxAttempts) {
              await new Promise(resolve => setTimeout(resolve, delay * attempt));
            }
          }
        }
        
        throw new Error(`Failed after ${maxAttempts} attempts: ${lastError.message}`);
      };
    }
  };
}

// 断路器装饰器
function circuitBreaker(failureThreshold = 5, timeout = 60000) {
  let failures = 0;
  let lastFailureTime = 0;
  let state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  
  return function(target, context) {
    if (context.kind === 'method') {
      return async function(...args) {
        const now = Date.now();
        
        // 检查断路器状态
        if (state === 'OPEN') {
          if (now - lastFailureTime > timeout) {
            state = 'HALF_OPEN';
          } else {
            throw new Error('Circuit breaker is OPEN');
          }
        }
        
        try {
          const result = await target.apply(this, args);
          
          // 成功时重置计数器
          if (state === 'HALF_OPEN') {
            state = 'CLOSED';
            failures = 0;
          }
          
          return result;
        } catch (error) {
          failures++;
          lastFailureTime = now;
          
          if (failures >= failureThreshold) {
            state = 'OPEN';
          }
          
          throw error;
        }
      };
    }
  };
}

// 使用示例
class ExternalApiService {
  @retry(3, 2000)
  @circuitBreaker(5, 30000)
  async fetchExternalData(endpoint) {
    const response = await fetch(`https://external-api.com${endpoint}`);
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    return response.json();
  }
}

高级装饰器模式

1. 装饰器工厂

javascript
// 装饰器工厂 - 创建可配置的装饰器
function createValidator(validationRules) {
  return function validate(target, context) {
    if (context.kind === 'method') {
      return function(...args) {
        // 验证参数
        validationRules.forEach((rule, index) => {
          const value = args[index];
          if (!rule.validator(value)) {
            throw new Error(`Parameter ${index}: ${rule.message}`);
          }
        });
        
        return target.apply(this, args);
      };
    }
  };
}

// 创建具体的验证装饰器
const validateUserInput = createValidator([
  {
    validator: (value) => typeof value === 'string' && value.length > 0,
    message: 'Name must be a non-empty string'
  },
  {
    validator: (value) => typeof value === 'number' && value >= 0,
    message: 'Age must be a non-negative number'
  }
]);

class UserService {
  @validateUserInput
  createUser(name, age) {
    return { name, age, id: Date.now() };
  }
}

2. 组合装饰器

javascript
// 装饰器组合工具
function compose(...decorators) {
  return function(target, context) {
    return decorators.reduceRight((acc, decorator) => {
      return decorator(acc, context) || acc;
    }, target);
  };
}

// 创建组合装饰器
const apiMethod = compose(
  performance,
  retry(3, 1000),
  memoize(30000),
  authorize('user')
);

class ApiController {
  @apiMethod
  async getUserData(userId) {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }
}

3. 元数据装饰器

javascript
// 元数据装饰器
function metadata(key, value) {
  return function(target, context) {
    if (!target.metadata) {
      target.metadata = {};
    }
    target.metadata[key] = value;
    return target;
  };
}

// 路由装饰器
function route(path, method = 'GET') {
  return function(target, context) {
    return metadata('route', { path, method })(target, context);
  };
}

// 中间件装饰器
function middleware(...middlewares) {
  return function(target, context) {
    return metadata('middlewares', middlewares)(target, context);
  };
}

// 使用示例
class UserController {
  @route('/users/:id', 'GET')
  @middleware('auth', 'rateLimit')
  async getUser(req, res) {
    const user = await this.userService.findById(req.params.id);
    res.json(user);
  }
  
  @route('/users', 'POST')
  @middleware('auth', 'validation')
  async createUser(req, res) {
    const user = await this.userService.create(req.body);
    res.json(user);
  }
}

// 路由注册器
function registerRoutes(controller) {
  const prototype = Object.getPrototypeOf(controller);
  const methods = Object.getOwnPropertyNames(prototype);
  
  methods.forEach(methodName => {
    const method = prototype[methodName];
    if (method.metadata?.route) {
      const { path, method: httpMethod } = method.metadata.route;
      const middlewares = method.metadata.middlewares || [];
      
      console.log(`Registering route: ${httpMethod} ${path}`);
      // 实际的路由注册逻辑
    }
  });
}

实际应用场景

1. React组件装饰器

javascript
// React组件装饰器
function withLoading(WrappedComponent) {
  return function LoadingWrapper(props) {
    const [loading, setLoading] = React.useState(false);
    
    const withLoadingProps = {
      ...props,
      setLoading,
      loading
    };
    
    if (loading) {
      return <div className="loading">Loading...</div>;
    }
    
    return <WrappedComponent {...withLoadingProps} />;
  };
}

function withErrorBoundary(WrappedComponent) {
  return class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false };
    }
    
    static getDerivedStateFromError(error) {
      return { hasError: true };
    }
    
    componentDidCatch(error, errorInfo) {
      console.error('Error caught by boundary:', error, errorInfo);
    }
    
    render() {
      if (this.state.hasError) {
        return <div className="error">Something went wrong.</div>;
      }
      
      return <WrappedComponent {...this.props} />;
    }
  };
}

// 使用装饰器
@withErrorBoundary
@withLoading
class UserProfile extends React.Component {
  async componentDidMount() {
    this.props.setLoading(true);
    try {
      const user = await fetchUser(this.props.userId);
      this.setState({ user });
    } finally {
      this.props.setLoading(false);
    }
  }
  
  render() {
    return (
      <div className="user-profile">
        <h1>{this.state.user?.name}</h1>
        <p>{this.state.user?.email}</p>
      </div>
    );
  }
}

2. Vue组件装饰器

javascript
// Vue组件装饰器
function VueComponent(options) {
  return function(target) {
    return {
      ...options,
      data() {
        return new target().data || {};
      },
      methods: Object.getOwnPropertyNames(target.prototype)
        .filter(name => name !== 'constructor')
        .reduce((methods, name) => {
          methods[name] = target.prototype[name];
          return methods;
        }, {})
    };
  };
}

function Prop(options = {}) {
  return function(target, propertyKey) {
    if (!target.constructor.props) {
      target.constructor.props = {};
    }
    target.constructor.props[propertyKey] = options;
  };
}

// 使用示例
@VueComponent({
  template: `
    <div class="user-card">
      <h3>{{ name }}</h3>
      <p>{{ email }}</p>
      <button @click="updateUser">Update</button>
    </div>
  `
})
class UserCard {
  @Prop({ type: String, required: true })
  name;
  
  @Prop({ type: String, required: true })
  email;
  
  data = {
    loading: false
  };
  
  @debounce(300)
  updateUser() {
    this.loading = true;
    // 更新用户逻辑
  }
}

3. 数据模型装饰器

javascript
// 数据模型装饰器
function Model(tableName) {
  return function(target) {
    target.tableName = tableName;
    target.fields = {};
    return target;
  };
}

function Field(options = {}) {
  return function(target, propertyKey) {
    if (!target.constructor.fields) {
      target.constructor.fields = {};
    }
    target.constructor.fields[propertyKey] = options;
  };
}

function Validate(validator) {
  return function(target, propertyKey) {
    const originalSetter = Object.getOwnPropertyDescriptor(target, propertyKey)?.set;
    
    Object.defineProperty(target, propertyKey, {
      set(value) {
        if (!validator(value)) {
          throw new Error(`Invalid value for ${propertyKey}`);
        }
        if (originalSetter) {
          originalSetter.call(this, value);
        } else {
          this[`_${propertyKey}`] = value;
        }
      },
      get() {
        return this[`_${propertyKey}`];
      }
    });
  };
}

// 使用示例
@Model('users')
class User {
  @Field({ type: 'string', primaryKey: true })
  id;
  
  @Field({ type: 'string', required: true })
  @Validate(value => typeof value === 'string' && value.length > 0)
  name;
  
  @Field({ type: 'string', unique: true })
  @Validate(value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
  email;
  
  @Field({ type: 'number', min: 0 })
  @Validate(value => typeof value === 'number' && value >= 0)
  age;
}

性能考虑

1. 装饰器性能优化

javascript
// 性能优化的装饰器实现
function optimizedMemoize(ttl = 60000) {
  // 使用WeakMap避免内存泄漏
  const cache = new WeakMap();
  
  return function(target, context) {
    if (context.kind === 'method') {
      return function(...args) {
        // 为每个实例创建独立的缓存
        if (!cache.has(this)) {
          cache.set(this, new Map());
        }
        
        const instanceCache = cache.get(this);
        const key = JSON.stringify(args);
        const cached = instanceCache.get(key);
        
        if (cached && Date.now() - cached.timestamp < ttl) {
          return cached.value;
        }
        
        const result = target.apply(this, args);
        instanceCache.set(key, { value: result, timestamp: Date.now() });
        
        // 定期清理过期缓存
        if (instanceCache.size > 100) {
          this.cleanupCache(instanceCache, ttl);
        }
        
        return result;
      };
    }
  };
}

// 缓存清理方法
function cleanupCache(cache, ttl) {
  const now = Date.now();
  for (const [key, value] of cache.entries()) {
    if (now - value.timestamp > ttl) {
      cache.delete(key);
    }
  }
}

2. 装饰器链优化

javascript
// 优化装饰器链的执行顺序
function createOptimizedDecorator(decorators) {
  // 分析装饰器依赖关系
  const sortedDecorators = sortDecoratorsByPriority(decorators);
  
  return function(target, context) {
    return sortedDecorators.reduce((acc, decorator) => {
      return decorator(acc, context) || acc;
    }, target);
  };
}

function sortDecoratorsByPriority(decorators) {
  return decorators.sort((a, b) => {
    const priorityA = a.priority || 0;
    const priorityB = b.priority || 0;
    return priorityB - priorityA; // 高优先级先执行
  });
}

// 为装饰器添加优先级
performance.priority = 10; // 性能监控优先级最高
authorize.priority = 9;    // 权限验证次之
memoize.priority = 5;      // 缓存优先级中等
debounce.priority = 1;     // 防抖优先级最低

最佳实践

1. 装饰器设计原则

javascript
// 1. 单一职责原则
// ❌ 错误:一个装饰器做太多事情
function badDecorator(target, context) {
  // 同时处理缓存、日志、权限验证
  return function(...args) {
    // 权限检查
    if (!this.hasPermission()) throw new Error('No permission');
    
    // 缓存检查
    const cached = this.getCache(args);
    if (cached) return cached;
    
    // 执行方法
    const result = target.apply(this, args);
    
    // 记录日志
    console.log('Method executed');
    
    // 缓存结果
    this.setCache(args, result);
    
    return result;
  };
}

// ✅ 正确:每个装饰器只负责一件事
const secureApiMethod = compose(
  authorize('user'),    // 只负责权限验证
  memoize(30000),      // 只负责缓存
  performance,         // 只负责性能监控
  audit('API_CALL')    // 只负责审计日志
);

2. 装饰器可组合性

javascript
// 2. 确保装饰器可以安全组合
function safeDecorator(target, context) {
  // 保存原始方法的元数据
  const originalMetadata = target.metadata || {};
  
  const decoratedMethod = function(...args) {
    // 装饰器逻辑
    return target.apply(this, args);
  };
  
  // 保持元数据
  decoratedMethod.metadata = {
    ...originalMetadata,
    decoratedBy: [...(originalMetadata.decoratedBy || []), 'safeDecorator']
  };
  
  return decoratedMethod;
}

3. 错误处理

javascript
// 3. 装饰器中的错误处理
function robustDecorator(target, context) {
  return function(...args) {
    try {
      return target.apply(this, args);
    } catch (error) {
      // 记录错误但不阻止执行
      console.error(`Error in ${context.name}:`, error);
      
      // 可以选择重新抛出或返回默认值
      if (error instanceof CriticalError) {
        throw error;
      }
      
      return null; // 或其他默认值
    }
  };
}

4. 类型安全(TypeScript)

typescript
// 4. 类型安全的装饰器
function typedDecorator<T extends (...args: any[]) => any>(
  target: T,
  context: ClassMethodDecoratorContext
): T {
  return ((...args: Parameters<T>): ReturnType<T> => {
    // 装饰器逻辑
    return target.apply(this, args);
  }) as T;
}

// 泛型装饰器工厂
function createTypedValidator<T extends any[]>(
  validators: { [K in keyof T]: (value: T[K]) => boolean }
) {
  return function<F extends (...args: T) => any>(
    target: F,
    context: ClassMethodDecoratorContext
  ): F {
    return ((...args: T): ReturnType<F> => {
      validators.forEach((validator, index) => {
        if (!validator(args[index])) {
          throw new Error(`Invalid argument at position ${index}`);
        }
      });
      
      return target.apply(this, args);
    }) as F;
  };
}

调试和测试

1. 装饰器调试技巧

javascript
// 调试装饰器
function debug(target, context) {
  const originalMethod = target;
  
  return function(...args) {
    console.group(`🔍 Debug: ${context.name}`);
    console.log('Arguments:', args);
    console.log('Context:', this);
    
    const start = performance.now();
    let result;
    let error;
    
    try {
      result = originalMethod.apply(this, args);
      
      if (result instanceof Promise) {
        return result
          .then(value => {
            console.log('Async Result:', value);
            console.log(`⏱️ Duration: ${performance.now() - start}ms`);
            console.groupEnd();
            return value;
          })
          .catch(err => {
            console.error('Async Error:', err);
            console.log(`⏱️ Duration: ${performance.now() - start}ms`);
            console.groupEnd();
            throw err;
          });
      } else {
        console.log('Result:', result);
        console.log(`⏱️ Duration: ${performance.now() - start}ms`);
        console.groupEnd();
        return result;
      }
    } catch (err) {
      console.error('Error:', err);
      console.log(`⏱️ Duration: ${performance.now() - start}ms`);
      console.groupEnd();
      throw err;
    }
  };
}

// 使用调试装饰器
class DebugService {
  @debug
  async fetchData(id) {
    const response = await fetch(`/api/data/${id}`);
    return response.json();
  }
}

2. 装饰器单元测试

javascript
// 装饰器测试工具
function createTestableDecorator(decorator) {
  return {
    decorator,
    test: (target, context, ...args) => {
      const decoratedMethod = decorator(target, context);
      return decoratedMethod.apply({}, args);
    }
  };
}

// 测试示例
describe('Memoize Decorator', () => {
  let callCount = 0;
  
  const testMethod = function(x, y) {
    callCount++;
    return x + y;
  };
  
  const testableDecorator = createTestableDecorator(memoize(1000));
  
  beforeEach(() => {
    callCount = 0;
  });
  
  it('should cache results', () => {
    const result1 = testableDecorator.test(testMethod, { name: 'add' }, 1, 2);
    const result2 = testableDecorator.test(testMethod, { name: 'add' }, 1, 2);
    
    expect(result1).toBe(3);
    expect(result2).toBe(3);
    expect(callCount).toBe(1); // 只调用一次,第二次使用缓存
  });
  
  it('should not cache different arguments', () => {
    testableDecorator.test(testMethod, { name: 'add' }, 1, 2);
    testableDecorator.test(testMethod, { name: 'add' }, 2, 3);
    
    expect(callCount).toBe(2); // 不同参数,调用两次
  });
});

装饰器生态系统

1. 流行的装饰器库

javascript
// reflect-metadata - 元数据支持
import 'reflect-metadata';

function Injectable(target) {
  Reflect.defineMetadata('injectable', true, target);
  return target;
}

function Inject(token) {
  return function(target, propertyKey, parameterIndex) {
    const existingTokens = Reflect.getOwnMetadata('inject-tokens', target) || [];
    existingTokens[parameterIndex] = token;
    Reflect.defineMetadata('inject-tokens', existingTokens, target);
  };
}

// 依赖注入示例
@Injectable
class UserService {
  constructor(
    @Inject('HttpClient') private http,
    @Inject('Logger') private logger
  ) {}
  
  async getUser(id) {
    this.logger.info(`Fetching user ${id}`);
    return this.http.get(`/users/${id}`);
  }
}

2. 框架集成

javascript
// Express.js 装饰器
function Controller(basePath = '') {
  return function(target) {
    target.basePath = basePath;
    return target;
  };
}

function Get(path = '') {
  return function(target, propertyKey, descriptor) {
    if (!target.constructor.routes) {
      target.constructor.routes = [];
    }
    
    target.constructor.routes.push({
      method: 'GET',
      path,
      handler: propertyKey
    });
    
    return descriptor;
  };
}

function Post(path = '') {
  return function(target, propertyKey, descriptor) {
    if (!target.constructor.routes) {
      target.constructor.routes = [];
    }
    
    target.constructor.routes.push({
      method: 'POST',
      path,
      handler: propertyKey
    });
    
    return descriptor;
  };
}

// 使用示例
@Controller('/api/users')
class UserController {
  @Get('/')
  async getAllUsers(req, res) {
    const users = await this.userService.findAll();
    res.json(users);
  }
  
  @Get('/:id')
  async getUser(req, res) {
    const user = await this.userService.findById(req.params.id);
    res.json(user);
  }
  
  @Post('/')
  async createUser(req, res) {
    const user = await this.userService.create(req.body);
    res.json(user);
  }
}

// 路由自动注册
function registerController(app, ControllerClass) {
  const controller = new ControllerClass();
  const basePath = ControllerClass.basePath || '';
  const routes = ControllerClass.routes || [];
  
  routes.forEach(route => {
    const fullPath = basePath + route.path;
    const method = route.method.toLowerCase();
    const handler = controller[route.handler].bind(controller);
    
    app[method](fullPath, handler);
    console.log(`Registered route: ${route.method} ${fullPath}`);
  });
}

未来发展

1. 标准化进程

javascript
// ES2024+ 装饰器新特性
class MyClass {
  // 自动访问器装饰器
  @reactive
  accessor count = 0;
  
  // 装饰器元数据
  @metadata({ version: '1.0', author: 'developer' })
  method() {
    return 'Hello World';
  }
}

// 装饰器元数据访问
function getMethodMetadata(target, methodName) {
  return target[Symbol.metadata]?.[methodName];
}

2. 工具链支持

javascript
// Babel 插件配置
// babel.config.js
module.exports = {
  plugins: [
    ['@babel/plugin-proposal-decorators', { 
      version: '2023-05' 
    }]
  ]
};

// TypeScript 配置
// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "useDefineForClassFields": false
  }
}

总结

装饰器的优势

  1. 代码复用 - 横切关注点的优雅解决方案
  2. 关注点分离 - 业务逻辑与基础设施代码分离
  3. 可组合性 - 多个装饰器可以灵活组合
  4. 声明式编程 - 更直观的代码表达方式

使用建议

  1. 适度使用 - 避免过度装饰导致代码难以理解
  2. 文档完善 - 为自定义装饰器提供清晰的文档
  3. 测试覆盖 - 确保装饰器逻辑有充分的测试
  4. 性能考虑 - 注意装饰器对性能的影响

最佳实践总结

javascript
// 装饰器最佳实践检查清单
const decoratorBestPractices = {
  design: [
    '单一职责原则',
    '可组合性设计',
    '错误处理机制',
    '类型安全保证'
  ],
  
  implementation: [
    '性能优化考虑',
    '内存泄漏防护',
    '元数据保持',
    '调试友好性'
  ],
  
  testing: [
    '单元测试覆盖',
    '集成测试验证',
    '边界条件测试',
    '性能基准测试'
  ],
  
  documentation: [
    '使用示例提供',
    '参数说明清晰',
    '副作用说明',
    '兼容性说明'
  ]
};

装饰器模式为JavaScript开发带来了强大的元编程能力,合理使用可以显著提升代码的可维护性和可读性。随着标准化进程的推进,装饰器将成为现代JavaScript开发的重要工具。


装饰器不仅仅是语法糖,它代表了一种编程思维的转变——从命令式到声明式,从继承到组合。掌握装饰器模式,将为你的JavaScript开发打开新的可能性。

vitepress开发指南