# 装饰器

装饰器 是一种特殊类型的声明,它能够被附加到 类声明方法访问符属性参数 上。 装饰器使用 @expression 这种形式,expression 求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

定义装饰器的时候,参数 最多有三个targetnamedescriptor

Decorators 的本质是利用了 ES5 的 Object.defineProperty 属性,这三个参数其实是和 Object.defineProperty (opens new window) 参数一致的,因此不能更改。

# 启用装饰器

tsconfig.jsoncompilerOptions.experimentalDecorators 设置为 true




 



{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}
1
2
3
4
5
6

# 装饰器种类

# 作用于类的装饰器

类装饰器只有一个参数:target

 









function isAnimal(target: any) {
  target.isAnimal = true;
}
@isAnimal
class Cat {
  // ...
}

console.log((Cat as any).isAnimal); // true
1
2
3
4
5
6
7
8
9

# 作用于类属性的装饰器

属性装饰器有三个参数:targetnamedescriptor

function readonly(target: any, name: string, descriptor: PropertyDescriptor) {
  descriptor.writable = false;
}

class Cat {
  @readonly
  say() {
    console.log("meow ~");
  }
}

const kitty = new Cat();
kitty.say = function() {
  console.log("woof !");
}; // Error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 装饰器的编译

# 编译前

@annotation
class MyClass {}

function annotation(target: any) {
  target.annotated = true;
}
1
2
3
4
5
6

# TSC 编译后

var __decorate =
  (this && this.__decorate) ||
  function(decorators, target, key, desc) {
    var c = arguments.length,
      r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc,
      d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else
      for (var i = decorators.length - 1; i >= 0; i--)
        if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
  };

var MyClass = /** @class */ (function() {
  function MyClass() {}
  MyClass.prototype.method = function() {};
  __decorate([readonly], MyClass.prototype, "method", null);
  return MyClass;
})();

function readonly(target, name, descriptor) {
  descriptor.writable = false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 精简整理一下







 


 
















var __decorate = function(decorators, target, key, desc) {
  var d;
  var r;

  r = target;

  for (var i = decorators.length - 1; i >= 0; i--) {
    d = decorators[i];
    if (d) {
      r = d(r) || r;
    }
  }

  return r;
};

var MyClass = /** @class */ (function() {
  function MyClass() {}
  MyClass = __decorate([annotation], MyClass);
  return MyClass;
})();

function annotation(target) {
  target.annotated = true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Babel 编译后



 





var _class;

let MyClass = annotation((_class = class MyClass {})) || _class;

function annotation(target) {
  target.annotated = true;
}
1
2
3
4
5
6
7

# 装饰器原理

从上面可以看到对于类的装饰,其原理就是:







 

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;
1
2
3
4
5
6
7

# 多个装饰器

function f() {
  console.log("f(): evaluated");
  return function(target, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("f(): called");
  };
}

function g() {
  console.log("g(): evaluated");
  return function(target, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("g(): called");
  };
}

class C {
  @f()
  @g()
  method() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

console:

f(): evaluated
g(): evaluated
g(): called
f(): called
1
2
3
4

TIP

如果同一个方法有多个装饰器会 由内向外执行,从 装饰器的编译结果 就可以看出。

# 其他

# 装饰模式 VS 适配器模式

装饰模式和适配器模式都是 包装模式 (Wrapper Pattern),它们都是通过封装其他对象达到设计的目的的,但是它们的形态有很大区别。

  • 适配器模式 我们使用的场景比较多,比如连接不同数据库的情况,你需要包装现有的模块接口,从而使之适配数据库 —— 好比你手机使用转接口来适配插座那样。

  • 装饰模式 不一样,仅仅包装现有的模块,使之 “更加华丽” ,并不会影响原有接口的功能 —— 好比你给手机添加一个外壳罢了,并不影响手机原有的通话、充电等功能。

# 在线编译

# 参考