docs: edit chapter decorator
This commit is contained in:
@@ -106,12 +106,13 @@ type Decorator = (
|
||||
context: {
|
||||
kind: string;
|
||||
name: string | symbol;
|
||||
addInitializer(initializer: () => void): void;
|
||||
|
||||
// 以下属性只在某些使用场合存在:
|
||||
static: boolean;
|
||||
private: boolean;
|
||||
access: {get: () => unknown, set: (value: unknown) => void};
|
||||
addInitializer?(initializer: () => void): void;
|
||||
static?: boolean;
|
||||
private?: boolean;
|
||||
access: {
|
||||
get?(): unknown;
|
||||
set?(value: unknown): void;
|
||||
};
|
||||
}
|
||||
) => void | ReplacementValue;
|
||||
```
|
||||
@@ -132,26 +133,28 @@ function decorator(
|
||||
|
||||
上面是一个装饰器函数,其中第二个参数`context`的类型就可以写成`ClassMethodDecoratorContext`。
|
||||
|
||||
`context`对象有以下属性。
|
||||
`context`对象的属性,根据所装饰对象的不同而不同,其中只有两个属性(`kind`和`name`)是必有的,其他都是可选的。
|
||||
|
||||
(1)`kind`:字符串,表示装饰器类型,可能取以下的值。
|
||||
(1)`kind`:字符串,表示所装饰对象的类型,可能取以下的值。
|
||||
|
||||
- 'class'
|
||||
- 'method'
|
||||
- 'getter'
|
||||
- 'setter'
|
||||
- 'accessor'
|
||||
- 'field'
|
||||
- 'accessor'
|
||||
|
||||
这表示一共有六种类型的装饰器。
|
||||
这表示一共有六种类型的装饰器。本章只介绍前五种装饰器,最后一种`accessor`暂时略过。
|
||||
|
||||
(2)`name`:字符串或者 Symbol 值,所装饰对象的名字,比如类名、属性名等。
|
||||
|
||||
(3)`addInitializer()`:函数,用来在类的初始化阶段,对方法进行一些处理。以前,这些处理通常放在构造函数里面,早于方法本身执行,现在改为放在装饰器的`context`对象里面,具体例子请参阅《方法装饰器》一节。
|
||||
(3)`addInitializer()`:函数,用来添加类的初始化逻辑。以前,这些逻辑通常放在构造函数里面,对方法进行初始化,现在改成以函数形式传入`addInitializer()`方法。注意,`addInitializer()`没有返回值。
|
||||
|
||||
注意,`addInitializer()`函数没有返回值。
|
||||
(4)`private`:布尔值,表示所装饰的对象是否为类的私有成员。
|
||||
|
||||
(4)`private`:布尔值,表示所装饰的方法或属性,是否为私有。
|
||||
(5)`static`:布尔值,表示所装饰的对象是否为类的静态成员。
|
||||
|
||||
(6)`access`:一个对象,包含了某个值的 get 和 set 方法。
|
||||
|
||||
## 类装饰器
|
||||
|
||||
@@ -168,49 +171,41 @@ type ClassDecorator = (
|
||||
) => Function | void;
|
||||
```
|
||||
|
||||
请看下面的例子。
|
||||
类装饰器接受两个参数:`value`(当前类本身)和`context`(上下文对象)。其中,`context`对象的`kind`属性固定为字符串`class`。
|
||||
|
||||
类装饰器一般用来对类进行操作,可以不返回任何值,请看下面的例子。
|
||||
|
||||
```typescript
|
||||
class InstanceCollector {
|
||||
instances = new Set();
|
||||
install = (value:any, {kind}:any) => {
|
||||
if (kind === 'class') {
|
||||
const _this = this;
|
||||
return function (...args:any[]) {
|
||||
const inst = new value(...args);
|
||||
_this.instances.add(inst);
|
||||
return value;
|
||||
} as unknown as typeof MyClass;
|
||||
}
|
||||
return;
|
||||
};
|
||||
function Greeter(value, context) {
|
||||
if (context.kind === 'class') {
|
||||
value.prototype.greet = function () {
|
||||
console.log('你好');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const collector = new InstanceCollector();
|
||||
@Greeter
|
||||
class User {}
|
||||
|
||||
@collector.install
|
||||
class MyClass {}
|
||||
|
||||
const inst1 = new MyClass();
|
||||
const inst2 = new MyClass();
|
||||
const inst3 = new MyClass();
|
||||
|
||||
collector.instances // new Set([inst1, inst2, inst3])
|
||||
let u = new User();
|
||||
u.greet(); // "你好"
|
||||
```
|
||||
|
||||
上面示例中,类装饰器`@collector.install`将所有实例加入一个集合变量`collector.instances`。
|
||||
上面示例中,类装饰器`@Greeter`在类`User`的原型对象上,添加了一个`greet()`方法,实例就可以直接使用该方法。
|
||||
|
||||
类装饰器返回的函数,会作为新的构造函数。
|
||||
类装饰器可以返回一个函数,替代当前类的构造方法。
|
||||
|
||||
```typescript
|
||||
function countInstances(value:any, context:any) {
|
||||
let instanceCount = 0;
|
||||
|
||||
const wrapper = function (...args:any[]) {
|
||||
instanceCount++;
|
||||
const instance = new value(...args);
|
||||
instance.count = instanceCount;
|
||||
return instance;
|
||||
} as unknown as typeof MyClass;
|
||||
|
||||
wrapper.prototype = value.prototype; // A
|
||||
return wrapper;
|
||||
}
|
||||
@@ -223,13 +218,16 @@ inst1 instanceof MyClass // true
|
||||
inst1.count // 1
|
||||
```
|
||||
|
||||
上面示例实现了实例的计数。为了确保`wrapper()`的返回值是`MyClass`的示例,特别加入`A`行,确保两者的原型对象是一致的。否则,新的构造函数`wrapper`的原型对象,与`MyClass`不同,通不过`instanceof`运算符。
|
||||
上面示例中,类装饰器`@countInstances`返回一个函数,替换了类`MyClass`的构造方法。新的构造方法实现了实例的计数,每新建一个实例,计数器就会加一,并且对实例添加`count`属性,表示当前实例的编号。
|
||||
|
||||
类装饰器也可以直接返回一个新的类。
|
||||
注意,上例为了确保新构造方法继承定义在`MyClass`的原型之上的成员,特别加入`A`行,确保两者的原型对象是一致的。否则,新的构造函数`wrapper`的原型对象,与`MyClass`不同,通不过`instanceof`运算符。
|
||||
|
||||
类装饰器也可以返回一个新的类,替代原来所装饰的类。
|
||||
|
||||
```typescript
|
||||
function countInstances(value:any, context:any) {
|
||||
let instanceCount = 0;
|
||||
|
||||
return class extends value {
|
||||
constructor(...args:any[]) {
|
||||
super(...args);
|
||||
@@ -249,7 +247,7 @@ inst1.count // 1
|
||||
|
||||
上面示例中,`@countInstances`返回一个`MyClass`的子类。
|
||||
|
||||
下面的例子是通过类装饰器,禁止使用`new`命令调用类。
|
||||
下面的例子是通过类装饰器,禁止使用`new`命令新建类的实例。
|
||||
|
||||
```typescript
|
||||
function functionCallable(
|
||||
@@ -295,13 +293,23 @@ type ClassMethodDecorator = (
|
||||
) => Function | void;
|
||||
```
|
||||
|
||||
它的上下文对象`context`有以下属性。
|
||||
根据上面的类型,方法装饰器是一个函数,接受两个参数:`value`和`context`。
|
||||
|
||||
- static:布尔值,表示是否为静态方法。
|
||||
- private:布尔值,表示是否为私有方法。
|
||||
参数`value`是方法本身,参数`context`是上下文对象,有以下属性。
|
||||
|
||||
- `kind`:值固定为字符串`method`,表示当前为方法装饰器。
|
||||
- `name`:所装饰的方法名,类型为字符串或 Symbol 值。
|
||||
- `static`:布尔值,表示是否为静态方法。该属性为只读属性。
|
||||
- `private`:布尔值,表示是否为私有方法。该属性为只读属性。
|
||||
- access:函数,表示方法的存取器,但是只能用来取值(只有`get()`方法),不能用来赋值(不能定义`set()`方法)。
|
||||
|
||||
方法装饰器会改写类的原始方法,实质等同于下面的操作。
|
||||
|
||||
```typescript
|
||||
function trace(decoratedMethod) {
|
||||
// ...
|
||||
}
|
||||
|
||||
class C {
|
||||
@trace
|
||||
toString() {
|
||||
@@ -309,24 +317,13 @@ class C {
|
||||
}
|
||||
}
|
||||
|
||||
function trace(decoratedMethod) {
|
||||
// 此处略
|
||||
}
|
||||
// `@trace` 等同于
|
||||
// C.prototype.toString = trace(C.prototype.toString);
|
||||
```
|
||||
|
||||
方法装饰器的实质是执行下面的操作。
|
||||
上面示例中,`@trace`是方法`toString()`的装饰器,它的效果等同于最后一行对`toString()`的改写。
|
||||
|
||||
```typescript
|
||||
class C {
|
||||
toString() {
|
||||
return 'C';
|
||||
}
|
||||
}
|
||||
|
||||
C.prototype.toString = trace(C.prototype.toString);
|
||||
```
|
||||
|
||||
如果装饰器返回一个新的函数,就会替代所装饰的对象。
|
||||
如果方法装饰器返回一个新的函数,就会替代所装饰的原始函数。
|
||||
|
||||
```typescript
|
||||
function replaceMethod() {
|
||||
@@ -383,12 +380,16 @@ function log(originalMethod:any, context:ClassMethodDecoratorContext) {
|
||||
|
||||
const person = new Person('张三');
|
||||
person.greet()
|
||||
// "LOG: Entering method 'greet'."
|
||||
// "Hello, my name is 张三."
|
||||
// "LOG: Exiting method 'greet'."
|
||||
// "LOG: Entering method 'greet'."
|
||||
// "Hello, my name is 张三."
|
||||
// "LOG: Exiting method 'greet'."
|
||||
```
|
||||
|
||||
下面是装饰器上下文对象的`addInitializer()`方法的例子。类的方法往往会在构造方法里面,进行`this`的绑定。
|
||||
上面示例中,装饰器`@log`的返回值是一个函数`replacementMethod`,替代了原始方法`greet()`。在`replacementMethod()`内部,通过执行`originalMethod.call()`完成了对原始方法的调用。
|
||||
|
||||
方法装饰器的参数`context`对象里面,有一个`addInitializer()`方法。它是一个钩子方法,用来在类的初始化阶段,添加回调函数,这个回调函数就是作为`addInitializer()`的参数传入的。
|
||||
|
||||
下面是`addInitializer()`方法的一个例子。我们知道,类的方法往往需要在构造方法里面,进行`this`的绑定。
|
||||
|
||||
```typescript
|
||||
class Person {
|
||||
@@ -404,9 +405,14 @@ class Person {
|
||||
console.log(`Hello, my name is ${this.name}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const g = new Person('张三').greet;
|
||||
g() // "Hello, my name is 张三."
|
||||
```
|
||||
|
||||
上面例子中,构造方法将`greet()`方法绑定了`this`,这行代码必须放在构造方法里面。现在,它可以移到`addInitializer()`。
|
||||
上面例子中,类`Person`的构造方法内部,将`this`与`greet()`方法进行了绑定。如果没有这一行,将`greet()`赋值给变量`g`进行调用,就会报错了。
|
||||
|
||||
`this`的绑定必须放在构造方法里面,因为这必须在类的初始化阶段完成。现在,它可以移到方法装饰器的`addInitializer()`里面。
|
||||
|
||||
```typescript
|
||||
function bound(
|
||||
@@ -424,6 +430,8 @@ function bound(
|
||||
|
||||
上面示例中,绑定`this`转移到了`addInitializer()`方法里面。
|
||||
|
||||
下面再看一个例子,通过`addInitializer()`将选定的方法名,放入一个集合。
|
||||
|
||||
```typescript
|
||||
function collect(
|
||||
value,
|
||||
@@ -449,7 +457,7 @@ const inst = new C();
|
||||
inst.@collect // new Set(['toString', Symbol.iterator])
|
||||
```
|
||||
|
||||
上面示例中,装饰器`@collect`会将所装饰的成员名字,加入一个 Set 集合`collectedMethodKeys`。
|
||||
上面示例中,方法装饰器`@collect`会将所装饰的成员名字,加入一个 Set 集合`collectedMethodKeys`。
|
||||
|
||||
## 属性装饰器
|
||||
|
||||
@@ -469,9 +477,50 @@ type ClassFieldDecorator = (
|
||||
) => (initialValue: unknown) => unknown | void;
|
||||
```
|
||||
|
||||
注意,装饰器的第一个参数`value`的类型是`undefined`,这意味着这个参数实际上是不存在的,即不能从`value`获取目标属性的值。
|
||||
注意,装饰器的第一个参数`value`的类型是`undefined`,这意味着这个参数实际上没用的,装饰器不能从`value`获取所装饰属性的值,要用下文的方法获取。另外,第二个参数`context`对象的`kind`属性的值为字符串`field`,而不是“property”或“attribute”,这一点是需要注意的。
|
||||
|
||||
如果要获取属性的值,必须使用存取器,请看下面的例子。
|
||||
属性装饰器的返回值是一个函数,该函数会自动执行,用来对所装饰属性进行初始化。该函数的参数是所装饰属性的初始值,该函数的返回值是所装饰属性的最终值。
|
||||
|
||||
```typescript
|
||||
function logged(value, context) {
|
||||
const { kind, name } = context;
|
||||
if (kind === 'field') {
|
||||
return function (initialValue) {
|
||||
console.log(`initializing ${name} with value ${initialValue}`);
|
||||
return initialValue;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Color {
|
||||
@logged name = 'green';
|
||||
}
|
||||
|
||||
const color = new Color();
|
||||
// "initializing name with value green"
|
||||
```
|
||||
|
||||
上面示例中,属性装饰器`@logged`装饰属性`name`。`@logged`的返回值是一个函数,该函数用来对属性`name`进行初始化,它的参数`initialValue`就是属性`name`的初始值`green`。新建实例对象`color`时,该函数会自动执行。
|
||||
|
||||
属性装饰器的返回值函数,可以用来更改属性的初始值。
|
||||
|
||||
```typescript
|
||||
function twice() {
|
||||
return initialValue => initialValue * 2;
|
||||
}
|
||||
|
||||
class C {
|
||||
@twice
|
||||
field = 3;
|
||||
}
|
||||
|
||||
const inst = new C();
|
||||
inst.field // 6
|
||||
```
|
||||
|
||||
上面示例中,属性装饰器`@twice`返回一个函数,该函数的返回值是属性`field`的初始值乘以2,所以属性`field`的最终值是6。
|
||||
|
||||
属性装饰器的上下文对象`context`的`access`属性,提供所装饰属性的存取器,请看下面的例子。
|
||||
|
||||
```typescript
|
||||
let acc;
|
||||
@@ -490,29 +539,13 @@ class Color {
|
||||
const green = new Color();
|
||||
green.name // 'green'
|
||||
|
||||
acc.get.call(green) // 'green'
|
||||
acc.get(green) // 'green'
|
||||
|
||||
acc.set.call(green, 'red');
|
||||
acc.set(green, 'red');
|
||||
green.name // 'red'
|
||||
```
|
||||
|
||||
上面示例中,`@exposeAccess`是`name`属性的装饰器,它的第二个参数就是`name`的上下文对象,其中`access`属性包含了取值器(`get`)和存值器(`set`),可以对`name`属性进行取值和赋值。
|
||||
|
||||
下面的例子是更改属性的初始值。
|
||||
|
||||
```typescript
|
||||
function twice() {
|
||||
return initialValue => initialValue * 2;
|
||||
}
|
||||
|
||||
class C {
|
||||
@twice
|
||||
field = 3;
|
||||
}
|
||||
|
||||
const inst = new C();
|
||||
inst.field // 6
|
||||
```
|
||||
上面示例中,`access`包含了属性`name`的存取器,可以对该属性进行取值和赋值。
|
||||
|
||||
## getter 装饰器,setter 装饰器
|
||||
|
||||
|
||||
Reference in New Issue
Block a user