您现在的位置是:网站首页 > 装饰器模式(Decorator)的ES7实现文章详情

装饰器模式(Decorator)的ES7实现

装饰器模式是一种结构型设计模式,允许在不改变对象自身的基础上动态地添加功能。ES7引入了装饰器语法,使得在JavaScript中实现装饰器模式更加直观和优雅。通过装饰器,可以轻松地扩展类、方法或属性的行为,而无需修改原始代码。

装饰器模式的核心概念

装饰器模式的核心在于"包装":通过将一个对象放入另一个对象(装饰器)中,动态地添加职责。装饰器与被装饰对象实现相同的接口,因此对客户端代码透明。在ES7中,装饰器通过@符号实现,本质上是一个高阶函数,接收目标对象并返回修改后的版本。

装饰器的典型应用场景包括:

  • 日志记录
  • 权限控制
  • 性能监控
  • 数据验证
  • 缓存处理

ES7装饰器基础语法

ES7装饰器目前是Stage 3提案,需要通过Babel等工具转换才能使用。装饰器可以应用于:

  1. 类装饰器
  2. 类方法装饰器
  3. 类属性装饰器
  4. 访问器装饰器

基本语法结构如下:

@decorator
class MyClass {}

class MyClass {
  @decorator
  method() {}
  
  @decorator
  property = 'value';
  
  @decorator
  get accessor() {}
}

类装饰器实现

类装饰器接收构造函数作为参数,可以返回新的构造函数来替换原类。下面是一个添加混入功能的类装饰器示例:

function mixin(...mixins) {
  return function(target) {
    Object.assign(target.prototype, ...mixins);
  };
}

const LoggerMixin = {
  log(message) {
    console.log(`[${this.constructor.name}] ${message}`);
  }
};

const SerializableMixin = {
  serialize() {
    return JSON.stringify(this);
  }
};

@mixin(LoggerMixin, SerializableMixin)
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const user = new User('Alice', 30);
user.log('Hello'); // "[User] Hello"
console.log(user.serialize()); // "{"name":"Alice","age":30}"

方法装饰器详解

方法装饰器接收三个参数:

  1. 目标对象(类原型)
  2. 方法名
  3. 属性描述符

下面是一个测量方法执行时间的方法装饰器:

function measureTime(target, name, descriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    
    console.log(`Method ${name} took ${end - start}ms`);
    return result;
  };
  
  return descriptor;
}

class Calculator {
  @measureTime
  heavyCalculation() {
    let result = 0;
    for (let i = 0; i < 100000000; i++) {
      result += Math.sqrt(i);
    }
    return result;
  }
}

const calc = new Calculator();
calc.heavyCalculation(); // 控制台输出执行时间

属性装饰器应用

属性装饰器接收两个参数:

  1. 目标对象
  2. 属性名

下面是一个实现属性观察者的装饰器:

function observable(target, key) {
  let value = target[key];
  
  Object.defineProperty(target, key, {
    get() {
      return value;
    },
    set(newValue) {
      console.log(`Setting ${key} from ${value} to ${newValue}`);
      value = newValue;
    }
  });
}

class Person {
  @observable
  name = 'Anonymous';
  
  @observable
  age = 0;
}

const person = new Person();
person.name = 'Bob'; // 控制台输出: "Setting name from Anonymous to Bob"
person.age = 25;     // 控制台输出: "Setting age from 0 to 25"

访问器装饰器示例

访问器装饰器与方法装饰器类似,可以装饰getter和setter。下面是一个实现只读属性的装饰器:

function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

class Circle {
  constructor(radius) {
    this._radius = radius;
  }
  
  @readonly
  get diameter() {
    return this._radius * 2;
  }
}

const circle = new Circle(5);
console.log(circle.diameter); // 10
circle.diameter = 20; // TypeError: Cannot assign to read only property

装饰器工厂模式

装饰器工厂是返回装饰器函数的函数,允许通过参数配置装饰器行为。下面是一个可配置的缓存装饰器:

function cache(times) {
  return function(target, name, descriptor) {
    const originalMethod = descriptor.value;
    let cacheMap = new Map();
    let callCount = 0;
    
    descriptor.value = function(...args) {
      const key = JSON.stringify(args);
      
      if (cacheMap.has(key)) {
        return cacheMap.get(key);
      }
      
      const result = originalMethod.apply(this, args);
      cacheMap.set(key, result);
      
      if (++callCount > times) {
        cacheMap.delete(cacheMap.keys().next().value);
        callCount--;
      }
      
      return result;
    };
    
    return descriptor;
  };
}

class MathOperations {
  @cache(3)
  factorial(n) {
    console.log('Calculating factorial for', n);
    return n <= 1 ? 1 : n * this.factorial(n - 1);
  }
}

const math = new MathOperations();
console.log(math.factorial(5)); // 计算并缓存
console.log(math.factorial(5)); // 从缓存读取

多个装饰器的执行顺序

当多个装饰器应用于同一个目标时,执行顺序遵循特定规则:

  1. 装饰器工厂先从上到下执行
  2. 装饰器函数再从下到上执行
function decoratorA() {
  console.log('decoratorA factory');
  return function(target, name, descriptor) {
    console.log('decoratorA applied');
  };
}

function decoratorB() {
  console.log('decoratorB factory');
  return function(target, name, descriptor) {
    console.log('decoratorB applied');
  };
}

class Example {
  @decoratorA()
  @decoratorB()
  method() {}
}

// 控制台输出:
// decoratorA factory
// decoratorB factory
// decoratorB applied
// decoratorA applied

实际应用:API请求装饰器

结合装饰器模式与async/await,可以创建强大的API请求处理装饰器:

function apiEndpoint(method, path) {
  return function(target, name, descriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = async function(...args) {
      try {
        console.log(`Calling ${method} ${path}`);
        const response = await fetch(path, {
          method,
          headers: {
            'Content-Type': 'application/json'
          },
          body: method !== 'GET' ? JSON.stringify(args[0]) : undefined
        });
        
        if (!response.ok) {
          throw new Error(`API request failed: ${response.status}`);
        }
        
        return await response.json();
      } catch (error) {
        console.error(`API call ${name} failed:`, error);
        throw error;
      }
    };
    
    return descriptor;
  };
}

class UserService {
  @apiEndpoint('GET', '/api/users')
  static async getUsers() {}
  
  @apiEndpoint('POST', '/api/users')
  static async createUser(userData) {}
  
  @apiEndpoint('PUT', '/api/users/:id')
  static async updateUser({ id, ...userData }) {}
}

// 使用示例
async function main() {
  const users = await UserService.getUsers();
  const newUser = await UserService.createUser({ name: 'Alice' });
  const updatedUser = await UserService.updateUser({ id: 1, name: 'Bob' });
}

元数据反射与装饰器

结合Reflect Metadata提案,装饰器可以实现更强大的元编程能力。首先需要安装reflect-metadata包:

npm install reflect-metadata

然后可以使用类型元数据:

import 'reflect-metadata';

function type(type) {
  return function(target, key) {
    Reflect.defineMetadata('design:type', type, target, key);
  };
}

function validate(target, name, descriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args) {
    const paramTypes = Reflect.getMetadata('design:paramtypes', target, name);
    
    args.forEach((arg, index) => {
      const expectedType = paramTypes[index];
      if (arg !== null && arg !== undefined && !(arg instanceof expectedType)) {
        throw new Error(`Parameter ${index} must be of type ${expectedType.name}`);
      }
    });
    
    return originalMethod.apply(this, args);
  };
  
  return descriptor;
}

class MathService {
  @validate
  add(@type(Number) a, @type(Number) b) {
    return a + b;
  }
}

const math = new MathService();
console.log(math.add(2, 3)); // 5
console.log(math.add('2', 3)); // Error: Parameter 0 must be of type Number

装饰器组合实践

多个装饰器可以组合使用,创建复杂的行为。下面是一个结合日志、缓存和验证的示例:

function log() {
  return function(target, name, descriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args) {
      console.log(`Calling ${name} with args:`, args);
      const result = originalMethod.apply(this, args);
      console.log(`Method ${name} returned:`, result);
      return result;
    };
    
    return descriptor;
  };
}

function validateArgs(...validators) {
  return function(target, name, descriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args) {
      validators.forEach((validator, index) => {
        if (!validator(args[index])) {
          throw new Error(`Argument ${index} is invalid`);
        }
      });
      
      return originalMethod.apply(this, args);
    };
    
    return descriptor;
  };
}

class ProductService {
  @log()
  @validateArgs(
    val => typeof val === 'string',
    val => typeof val === 'number' && val > 0
  )
  createProduct(name, price) {
    return { id: Date.now(), name, price };
  }
}

const service = new ProductService();
const product = service.createProduct('Laptop', 999);
// 控制台输出调用和返回日志
service.createProduct('', -1); // 抛出验证错误

装饰器在React中的应用

虽然React组件通常是函数组件,但类组件仍然可以使用装饰器。下面是一个高阶组件装饰器示例:

function withLoading(WrappedComponent) {
  return class extends React.Component {
    state = { loading: true };
    
    async componentDidMount() {
      if (typeof this.props.loadData === 'function') {
        await this.props.loadData();
      }
      this.setState({ loading: false });
    }
    
    render() {
      if (this.state.loading) {
        return <div>Loading...</div>;
      }
      
      return <WrappedComponent {...this.props} />;
    }
  };
}

@withLoading
class UserProfile extends React.Component {
  render() {
    return <div>{this.props.user.name}'s Profile</div>;
  }
}

// 使用
<UserProfile 
  user={{ name: 'Alice' }}
  loadData={async () => {
    // 模拟API调用
    await new Promise(resolve => setTimeout(resolve, 1000));
  }}
/>

装饰器的局限性

虽然装饰器功能强大,但也存在一些限制:

  1. 不能装饰函数(非类方法)
  2. 属性装饰器不能直接修改属性值
  3. 静态方法和原型方法的装饰器参数不同
  4. 目前仍是Stage 3提案,语法可能变化
  5. 过度使用可能导致代码难以理解

性能考量

装饰器在运行时会有一定的性能开销,特别是在频繁调用的方法上。应当注意:

  1. 避免在装饰器中进行复杂计算
  2. 对于高频调用的方法,考虑是否真的需要装饰器
  3. 装饰器工厂只在初始化时执行一次,但装饰器函数会在每次访问时执行
// 低效的装饰器示例
function inefficientDecorator() {
  // 这个复杂计算会在每次装饰器调用时执行
  const heavyCalculation = Array(1000000).fill(0).map((_, i) => i * i);
  
  return function(target, name, descriptor) {
    // ...
  };
}

TypeScript中的装饰器

TypeScript已经支持装饰器语法(需要启用experimentalDecorators选项)。TypeScript装饰器与ES7提案略有不同,但概念相似。TypeScript还提供了额外的元数据能力:

// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

// 使用示例
import 'reflect-metadata';

function logType(target: any, key: string) {
  const type = Reflect.getMetadata('design:type', target, key);
  console.log(`${key} type: ${type.name}`);
}

class Example {
  @logType
  public name: string = 'test';
  
  @logType
  public age: number = 30;
}

// 控制台输出:
// name type: String
// age type: Number

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步