什么是 decorator,什么时候用 decorator

本文深入讲解JavaScript中的装饰器概念,探讨其应用场景与实现原理,并通过实际案例展示如何使用装饰器来增强代码的可读性和可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习 ES2015+的时候,转码已经非常的普遍。很多人都已经在实践中使用过了新的语言特性,或者至少是在教程里学习过。这些新特性之中经常让人挠头的莫属 decorator(装饰器,后文也不会翻译)了。

由于在 Angular2+的广泛使用,decorator 变得流行。在 Angular 里,由于有 TypeScript 所以用上了 decorator。但是在 javascript 里,decorator 还在 stage-2,也就是说会和 js 的更新一起发布。我们来看一下 decorator 是什么,如何使用它来让你的代码更加的简洁易懂。

什么是 decorator

它最简单的形式是一段代码的包装,也就是说装饰这段代码。这个概念也叫做高阶方法。这个模式已经使用的非常的多了,比如:

function doSomething(name) {
  console.log("Hello " + name);
}

function loggingDecorator(wrapped) {
  return function() {
    console.log("Starting...");
    const result = wrapped.apply(this, arguments);
    console.log("Finished");

    return result;
  };
}

const wrapped = loggingDecorator(doSomething);

这个例子生成了一个新的方法,在wrapped变量中,可以和doSomething一样被调用,并且行为也完全一致。唯一不同的地方是它会在被调用后输出一些日志。如:

doSomething('Graham');

// Hello, Graham

wrapped('Graham);

// Starting...
// Hello, Graham
// Finished

如何使用 decorator

Decorator 的语法稍微有点特殊,它以@开始,放在需要装饰的代码上方。

在写作的时候decorator已经进入了 Stage 2,也就是说基本上不会变了,但是还是可能会发生改变的。

理论上你可以在同一代码上使用任意多的 decorator,他们会以你声明的顺序执行。比如:

@log()
@immutable()
class Example {
  @time("demo")
  doSomething() {
    // ...
  }
}

上例中定义了另一个Example类,并且在里面使用了三个 decorator。两个作用在类上,一个作用在属性上:

  • @log可以访问类
  • @immutable可以让类只读 -- 也许它在新的实例上调用了Object.freeze
  • @time会记录一个方法执行使用了多长时间,并输出日志

目前,使用 decorator 需要转码工具的支持。因为还没有浏览器和 node 的版本支持 decorator。如果你使用的是 Babel,只要使用transform-decorators-legacy plugin就可以。注意里面的
legacy这个词,这个是 babel 为了支持 es5 的方式实现 decorator,也许最后会和语言标准有些不同。

什么时候使用 decorator

高阶方法在 javascript 中已经使用的非常多了,但是它还是很难作用于其他的代码段(比如类和类的属性)上,至少写起来非常的别扭。

Decorator 支持类和属性,很好的解决了上面的问题。以后的 javascript 标准里也许会赋予 decorator 更多的作用,处理很多之前没法优雅的处理的代码。

Decorator 的不同类型

目前的 decorator 还只支持类和类的成员,包括:属性、方法、getter 和 setter。

Decorator 的实质是一个放回方法的方法,并且会在里面以某种方式处理被装饰的代码段。这些 decorator 代码会在程序开始的时候运行一次,并且被装饰的代码会被 decorator 返回的值替换。

类成员 decorator

属性 decorator 作用在一个类成员上,无论是属性、方法、或者 getter 和 setter。这个 decorator 方法在调用的时候会传入三个参数:

  • target: 成员所在的类
  • name: 类成员的名字
  • descriptor: 类成员的 descriptor。这个是在Object.defineProperty里使用的对象。

这里使用经典的例子@readonly。它是这么实现的:

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

这里在属性的 descriptor 里更新了writable的值为 false。

这个 decorator 是这么使用在类成员上的:

class Example {
  a() {}

  @readonly
  b() {}
}

const e = new Example();
e.a = 1;
e.b = 2; // TypeError: Cannot assign to readonly property 'b' of object '#<Example>'

我们来看一个更有难度的例子。我们可以用不同的功能来取代被装饰的方法。比如,把所有的输入和输出都打印出来。

function log(target, name, descriptor) {
  const original = descriptor.value;
  if (typeof original === "function") {
    descriptor.value = function(...args) {
      console.log(`Arguments: ${args}`);
      try {
        const result = original.apply(this, args);
        console.log(`Result: ${result}`);
        return result;
      } catch (e) {
        console.log(`Error: ${e}`);
        throw e;
      }
    };
  }

  return descriptor;
}

原来的方法基本就完全被取代了,我们在里面加上了日志打印输入、输出。

注意我们用了...操作符来自动把输入的参数转化为一个数组,这样比之前处理arguments的写法简单了很多。

运行之后会得到:

class Example {
  @log
  sum(a, b) {
    return a + b;
  }
}

const e = new Example();

e.sum(1, 2);

// Arguments: 1,2
// Result: 3

你看到我们需要用一个有趣的语法来执行 decorator 方法。这一点可以单独写一篇文章来叙述了。简单来说apply方法可以让你指定所执行的方法的this和参数。

我们也可以让 decorator 接收参数。比如,我们可以这样重写log decorator。

function log(name) {
  return function decorator(t, n, descriptor) {
    const original = descriptor.value;
    if (typeof original === "function") {
      descriptor.value = function(...args) {
        console.log(`Arguments for ${name}: ${args}`);

        try {
          const result = original.apply(this, args);
        } catch (e) {}
      };
    }

    return descriptor;
  };
}

这就更加的复杂了,但是分解开来看你会发现:

  • 一个方法只接收一个参数的方法。 log只接收一个name参数。
  • 这个方法返回了一个方法,这个方法才是 decorator

这个之前所些的log decorator 基本是一样的,只是它使用了外部方法传入的name参数。

使用的时候是这样的:

class Example {
  @log("some tag")
  sum(a, b) {
    return a + b;
  }
}

const e = new Example();

e.sum(1, 2);
// Arguments for some tag: 1,2
// Result from some tag: 3

这样使用的结果是我们可以用某些 tag 来区分开不同的 log。

这样的写法可以运行是因为log('some tag')方法会被 javascript 运行时立即执行,然后把log方法的返回结果作为sum方法的 decorator。

类 decorator

类 decorator 会修饰整个类。Decorator 方法接收构造器方法作为唯一的参数。

注意类 decorator 作用域构造器方法,不是这个类的实例。也就是说如果你想修改类的实例等话你要自己写构造方法的包装函数。

一般来说,类 decorator 不如类成员 decorator 有用。因为你在这里可以实现的,都可以通过同样的方法调用一个方法来实现。总之,无论做什么你都需要在最后返回新的构造方法来代替就的构造方法。

我们来改造一下前面的log方法,让它来处理构造方法。

function log(Class) {
  return (...args) => {
    console.log(args);
    return new Class(...args);
  };
}

这里我们接受一个类作为参数,然后返回一个新的方法。这个方法会被作为构造方法使用。它只是简单的把参数打印到 console 里,返回一个类的实例。

比如:

@log
class Example {
  constructor(name, age) {}
}

const e = new Example("Graham", 12);
// ['Graham', 12]
console.log(e);
// Example {}

我们可以看到构造 Example 类的时候就会有参数的日志输出出来。Decorator 之后的类输出的实例也还是 Example 的实例。这正是我们要的效果。

给类 decorator 传入参数的办法和前面的类成员的 decorator 的方法是一样的:

function log(name) {
  return function decorator(Class) {
    return (...args) => {
      console.log(`Arguments for ${name}: args`);
      return new Class(...args);
    };
  };
}

@log("Demo")
class Example {
  constructor(name, age) {}
}

const e = new Example("Graham", 12);
// Arguments for Demo: args
console.log(e);
// Example {}

真实的例子

Core Decorators

有一个很不错的库叫做Core Decorators。这个库提供了很多有用的 decorator,并且已经在使用中了。这个库里常用到的功能有 timing、警告、只读等工具方法。

React

React 库使用了很多高阶组件。高阶组件其实就是 React 的组件,只不过写成了一个方法,并且包装了另外的一个组件。

尤其是在和 react-redux 库一起使用的时候,要写很多次的connect方法:

class MyComponent extends React.Component {}
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MyComponent);

然而,这些都可以用 decorator 来代替:

@connect(
  mapStateToProps,
  mapDispatchToProps
)
export default class MyComponent extends React.Component {}

MobX

MobX 库广泛的使用了 decorator,这样你很容易的把字段标记为 Observable 或者 Computed,把类标记为 Observer。

总结

类成员 decorator 提供了很好的方法来包装类里的代码。这样你可以非常容易的把通用的工具类代码作用的类或者类成员上。

### Python `@decorator` 语法糖的概念和用法 #### 概念 在 Python 中,`@decorator` 是一种用于修改函数或方法行为的语法糖。它本质上是一个高阶函数,能够接受另一个函数作为输入并返回一个新的函数[^2]。通过这种机制,可以在不改变原函数代码的情况下,为其添加额外的功能。 #### 使用方法 以下是关于如何定义和使用装饰器的具体说明: 1. **基本结构** 装饰器通常由一个外部函数包裹内部函数构成。内部函数负责执行原始函数前后的附加逻辑,并最终调用原始函数。 ```python def decorator_function(original_func): def wrapper_function(*args, **kwargs): # 执行一些前置操作 print("Before calling the original function.") result = original_func(*args, **kwargs) # 调用原始函数 # 执行一些后置操作 print("After calling the original function.") return result return wrapper_function ``` 2. **简化写法** 利用 `@decorator` 语法糖可以直接将装饰器应用于目标函数,而无需显式地重新赋值给变量。 ```python @decorator_function def target_function(): print("Inside the target function.") target_function() ``` 上述代码等价于: ```python def target_function(): print("Inside the target function.") decorated_target = decorator_function(target_function) decorated_target() ``` 3. **带参数的装饰器** 如果需要传递参数至装饰器本身,则需再嵌套一层函数来接收这些参数。 ```python def parameterized_decorator(param): def actual_decorator(func): def wrapper(*args, **kwargs): print(f"Using parameter: {param}") return func(*args, **kwargs) return wrapper return actual_decorator @parameterized_decorator("example_param") def another_function(): print("Another function executed.") ``` 4. **保留元数据** 默认情况下,经过装饰后的函数可能会丢失其原有的元信息(如名字、文档字符串等)。为了防止这种情况发生,推荐使用 `functools.wraps` 来复制必要的属性[^2]。 ```python from functools import wraps def metadata_preserving_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print("Preserving metadata...") return func(*args, **kwargs) return wrapper @metadata_preserving_decorator def sample_function(): """Sample docstring.""" pass print(sample_function.__name__) # 输出: sample_function print(sample_function.__doc__) # 输出: Sample docstring. ``` 5. **多重装饰器** 当同一个函数被多个装饰器修饰时,它们会按照从外向内的顺序依次生效。 ```python def outermost_decorator(func): def inner_wrapper(*args, **kwargs): print("Outermost layer applied first.") return func(*args, **kwargs) return inner_wrapper def innermost_decorator(func): def inner_wrapper(*args, **kwargs): print("Innermost layer applied last.") return func(*args, **kwargs) return inner_wrapper @outermost_decorator @innermost_decorator def test_multiple_decorators(): print("Function body execution.") test_multiple_decorators() ``` #### 实际案例 下面展示了一个实际场景中的装饰器应用——计算函数运行时间[^3]。 ```python import time def timing_decorator(func): def timed_execution(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) elapsed_time = time.time() - start_time print(f"{func.__name__} took {elapsed_time:.6f}s to complete.") return result return timed_execution @timing_decorator def compute_sum(n): total = sum(range(n)) return total compute_sum(1000000) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值