Decorator Pattern

装饰器模式的实现与应用场景

问题

什么是装饰器模式?如何在 JavaScript 中实现装饰器模式?

解答

装饰器模式允许在不修改原有对象的情况下,动态地给对象添加新的功能。

基础实现

// 基础组件
class Coffee {
  cost() {
    return 10;
  }

  description() {
    return '咖啡';
  }
}

// 装饰器基类
class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost();
  }

  description() {
    return this.coffee.description();
  }
}

// 具体装饰器:加牛奶
class MilkDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 3;
  }

  description() {
    return this.coffee.description() + ' + 牛奶';
  }
}

// 具体装饰器:加糖
class SugarDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 1;
  }

  description() {
    return this.coffee.description() + ' + 糖';
  }
}

// 使用:可以任意组合
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);

console.log(coffee.description()); // 咖啡 + 牛奶 + 糖
console.log(coffee.cost()); // 14

函数装饰器

// 通用函数装饰器
function withLogging(fn) {
  return function (...args) {
    console.log(`调用 ${fn.name},参数:`, args);
    const result = fn.apply(this, args);
    console.log(`返回值:`, result);
    return result;
  };
}

function withTiming(fn) {
  return function (...args) {
    const start = performance.now();
    const result = fn.apply(this, args);
    const end = performance.now();
    console.log(`${fn.name} 执行时间: ${end - start}ms`);
    return result;
  };
}

// 原始函数
function add(a, b) {
  return a + b;
}

// 组合装饰
const decoratedAdd = withLogging(withTiming(add));
decoratedAdd(1, 2);
// 调用 add,参数: [1, 2]
// add 执行时间: 0.01ms
// 返回值: 3

ES7 装饰器语法

// 方法装饰器
function readonly(target, key, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

function log(target, key, descriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args) {
    console.log(`调用 ${key},参数:`, args);
    return original.apply(this, args);
  };
  return descriptor;
}

class Calculator {
  @log
  @readonly
  add(a, b) {
    return a + b;
  }
}

// 类装饰器
function withTimestamp(Class) {
  return class extends Class {
    constructor(...args) {
      super(...args);
      this.createdAt = new Date();
    }
  };
}

@withTimestamp
class User {
  constructor(name) {
    this.name = name;
  }
}

实际应用:防抖装饰器

function debounce(delay) {
  return function (target, key, descriptor) {
    const original = descriptor.value;
    let timer = null;

    descriptor.value = function (...args) {
      clearTimeout(timer);
      timer = setTimeout(() => {
        original.apply(this, args);
      }, delay);
    };

    return descriptor;
  };
}

class SearchBox {
  @debounce(300)
  search(keyword) {
    console.log('搜索:', keyword);
    // 发起请求...
  }
}

关键点

  • 装饰器通过包装对象来扩展功能,不修改原对象
  • 多个装饰器可以叠加使用,顺序影响执行结果
  • 函数装饰器利用闭包和高阶函数实现
  • ES7 装饰器本质是语法糖,编译后仍是函数调用
  • 常见应用:日志、缓存、权限校验、防抖节流