阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 装饰器模式(Decorator)的ES7实现

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

作者:陈川 阅读数:49840人阅读 分类: JavaScript

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

装饰器模式是一种结构型设计模式,允许向现有对象动态添加新功能而不改变其结构。ES7引入的装饰器语法让这种模式在JavaScript中实现变得更加优雅。装饰器本质上是一个高阶函数,能够修改类、方法、属性或参数的行为。

装饰器基础语法

ES7装饰器使用@符号表示,可以应用于类、类方法、访问器、属性和参数。装饰器本质上是一个函数,接收目标对象的相关信息作为参数。

// 类装饰器
@decorator
class MyClass {}

// 方法装饰器
class MyClass {
  @decorator
  method() {}
}

// 属性装饰器
class MyClass {
  @decorator
  property = 'value'
}

// 访问器装饰器
class MyClass {
  @decorator
  get value() {}
}

实现简单的装饰器

装饰器函数接收三个参数:目标对象、属性名和属性描述符。对于类装饰器,只接收构造函数作为参数。

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

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

const calc = new Calculator()
calc.add(2, 3) // 输出: 调用 add 参数: 2,3

装饰器工厂

装饰器工厂是返回装饰器函数的函数,允许传递参数定制装饰器行为。

function logWithMessage(message) {
  return function(target, name, descriptor) {
    const original = descriptor.value
    descriptor.value = function(...args) {
      console.log(`${message}: 调用 ${name}`)
      return original.apply(this, args)
    }
    return descriptor
  }
}

class User {
  @logWithMessage('用户操作')
  login(username, password) {
    // 登录逻辑
  }
}

类装饰器实现

类装饰器应用于类构造函数,可以用来修改或替换类定义。

function sealed(constructor) {
  Object.seal(constructor)
  Object.seal(constructor.prototype)
}

@sealed
class Animal {
  constructor(name) {
    this.name = name
  }
  
  speak() {
    console.log(`${this.name} makes a noise.`)
  }
}

属性装饰器应用

属性装饰器可以用于观察属性变化或添加元数据。

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

class Circle {
  @readonly
  PI = 3.14159
  
  constructor(radius) {
    this.radius = radius
  }
}

const circle = new Circle(5)
circle.PI = 3.14 // 报错: Cannot assign to read only property 'PI'

多个装饰器组合

装饰器可以叠加使用,执行顺序是从下到上(声明时从近到远)。

function first() {
  console.log('first(): 装饰器工厂')
  return function(target, propertyKey, descriptor) {
    console.log('first(): 装饰器')
  }
}

function second() {
  console.log('second(): 装饰器工厂')
  return function(target, propertyKey, descriptor) {
    console.log('second(): 装饰器')
  }
}

class Example {
  @first()
  @second()
  method() {}
}
// 输出顺序:
// second(): 装饰器工厂
// first(): 装饰器工厂
// second(): 装饰器
// first(): 装饰器

实际应用场景

装饰器在Web开发中有多种实用场景,如日志记录、性能监测、表单验证等。

// 性能计时装饰器
function time(label = '') {
  return function(target, name, descriptor) {
    const original = descriptor.value
    descriptor.value = async function(...args) {
      const start = performance.now()
      const result = await original.apply(this, args)
      console.log(`${label || name} 耗时: ${performance.now() - start}ms`)
      return result
    }
    return descriptor
  }
}

class API {
  @time('获取用户数据')
  async fetchUser(id) {
    // 模拟API请求
    await new Promise(resolve => setTimeout(resolve, 500))
    return { id, name: 'John Doe' }
  }
}

元数据装饰器

装饰器可以用于添加元数据,配合Reflect Metadata实现更强大的功能。

import 'reflect-metadata'

function validate(type) {
  return function(target, key, descriptor) {
    Reflect.defineMetadata('validation:type', type, target, key)
    
    const original = descriptor.value
    descriptor.value = function(value) {
      if (typeof value !== type) {
        throw new Error(`参数 ${key} 必须是 ${type} 类型`)
      }
      return original.call(this, value)
    }
    return descriptor
  }
}

class Validator {
  @validate('string')
  setName(name) {
    this.name = name
  }
}

const validator = new Validator()
validator.setName('Alice') // 正常
validator.setName(123) // 报错: 参数 name 必须是 string 类型

自动绑定this

装饰器可以解决类方法中this丢失的问题,自动绑定实例。

function autobind(target, name, descriptor) {
  const originalMethod = descriptor.value
  return {
    configurable: true,
    get() {
      const boundFn = originalMethod.bind(this)
      Object.defineProperty(this, name, {
        value: boundFn,
        configurable: true,
        writable: true
      })
      return boundFn
    }
  }
}

class Button {
  @autobind
  handleClick() {
    console.log(this) // 始终指向Button实例
  }
}

const button = new Button()
document.addEventListener('click', button.handleClick)

装饰器与高阶组件

在React中,装饰器常用于创建高阶组件(HOC)。

function withLoading(WrappedComponent) {
  return class extends React.Component {
    state = { loading: true }
    
    async componentDidMount() {
      // 模拟数据加载
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.setState({ loading: false })
    }
    
    render() {
      return this.state.loading 
        ? <div>Loading...</div>
        : <WrappedComponent {...this.props} />
    }
  }
}

@withLoading
class UserProfile extends React.Component {
  render() {
    return <div>User Profile Content</div>
  }
}

装饰器的局限性

虽然装饰器功能强大,但需要注意几个限制:不能装饰函数(非类方法)、不能修改类继承关系、在静态分析工具中可能不被完全支持。此外,装饰器目前仍是ECMAScript的提案,需要使用Babel等工具转换才能运行。

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌