docs: edit chapter decorator

This commit is contained in:
ruanyf
2023-07-26 00:21:59 +08:00
parent b8a44da58c
commit d3313fbbfa

View File

@@ -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 装饰器