Skip to content

09. JS 装饰器

1. 装饰器是什么?

装饰器(Decorator)是一种特殊类型的声明,用于为类、方法、属性或访问器添加元编程功能。其本质是一个函数,通过@符号作为标识符,可以:

  • 修改类定义
  • 扩展方法行为
  • 添加元数据
  • 实现AOP(面向切面编程)

示例:

javascript
@log // 类装饰器
class Calculator {
  @memoize // 方法装饰器
  sum(a, b) {
    return a + b
  }
}

2. JavaScript 装饰器的起源

发展历程:

  • 2014 年首次出现在 TypeScript 1.5
  • 2018 年进入 TC39 提案 Stage 2
  • 2022 年更新为 Stage 3(当前状态)
  • 需要 Babel/TypeScript 转译支持

标准实现差异:

环境语法支持配置方式
Babel需要插件@babel/plugin-proposal-decorators
TypeScript实验特性tsconfig.json 中开启 experimentalDecorators

3. 类的装饰

基本结构:

javascript
function classDecorator(target) {
  return class extends target {
    // 扩展类功能
  }
}

@classDecorator
class MyClass {}

参数说明:

  • 参数 1:被装饰的类构造函数
  • 返回值:新的类构造函数

示例:添加版本信息

javascript
function withVersion(version) {
  return function(target) {
    target.prototype.version = version
    return target
  }
}

@withVersion('1.0.0')
class App {}

4. 方法或成员的装饰

方法装饰器签名:

javascript
function methodDecorator(target, name, descriptor) {
  const originalMethod = descriptor.value
  descriptor.value = function(...args) {
    // 方法逻辑扩展
    return originalMethod.apply(this, args)
  }
  return descriptor
}

参数说明:

参数说明
target类的原型对象
name被装饰成员的名称
descriptor属性描述符(Object.defineProperty 使用)

属性描述符结构:

javascript
{
  value: 当前值,
  enumerable: 是否可枚举,
  configurable: 是否可配置,
  writable: 是否可写
}

示例:防抖装饰器

javascript
function debounce(delay = 300) {
  return function(target, name, descriptor) {
    let timeout
    const original = descriptor.value
  
    descriptor.value = function(...args) {
      clearTimeout(timeout)
      timeout = setTimeout(() => {
        original.apply(this, args)
      }, delay)
    }
  
    return descriptor
  }
}

class SearchBox {
  @debounce(500)
  search(query) {
    // 搜索逻辑
  }
}

5. 为什么装饰器不用于函数

规范限制原因:

  1. 函数提升(Hoisting)导致装饰顺序不可控
  2. 箭头函数的 this 绑定问题
  3. 避免与函数表达式混淆

错误示例:

javascript
// 以下写法在规范中不被支持
@log
function myFunction() {}

替代方案:

javascript
// 使用高阶函数包装
const decoratedFn = log(function() {})

6. 实践:实现日志记录

完整日志装饰器实现:

javascript
function log(target, name, descriptor) {
  const original = descriptor.value

  descriptor.value = function(...args) {
    console.log(`[调用] ${name} 参数: ${JSON.stringify(args)}`)
    const start = Date.now()
    const result = original.apply(this, args)
    console.log(`[完成] ${name} 耗时: ${Date.now() - start}ms`)
    return result
  }

  return descriptor
}

class API {
  @log
  async fetchData(url) {
    const response = await fetch(url)
    return response.json()
  }
}

// 使用示例
const api = new API()
api.fetchData('https://api.example.com/data')

输出结果:

text
[调用] fetchData 参数: ["https://api.example.com/data"]
[完成] fetchData 耗时: 352ms

配置指南:

  1. 安装 Babel 支持
    bash
    npm install --save-dev @babel/plugin-proposal-decorators
  2. 配置 .babelrc
    json
    {
      "plugins": [
        ["@babel/plugin-proposal-decorators", { "version": "2022-03" }]
      ]
    }