Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d3313fb

Browse files
committedJul 25, 2023
docs: edit chapter decorator
1 parent b8a44da commit d3313fb

File tree

1 file changed

+116
-83
lines changed

1 file changed

+116
-83
lines changed
 

‎docs/decorator.md

+116-83
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,13 @@ type Decorator = (
106106
context: {
107107
kind: string;
108108
name: string | symbol;
109-
addInitializer(initializer: () => void): void;
110-
111-
// 以下属性只在某些使用场合存在:
112-
static: boolean;
113-
private: boolean;
114-
access: {get: () => unknown, set: (value: unknown) => void};
109+
addInitializer?(initializer: () => void): void;
110+
static?: boolean;
111+
private?: boolean;
112+
access: {
113+
get?(): unknown;
114+
set?(value: unknown): void;
115+
};
115116
}
116117
) => void | ReplacementValue;
117118
```
@@ -132,26 +133,28 @@ function decorator(
132133

133134
上面是一个装饰器函数,其中第二个参数`context`的类型就可以写成`ClassMethodDecoratorContext`
134135

135-
`context`对象有以下属性
136+
`context`对象的属性,根据所装饰对象的不同而不同,其中只有两个属性(`kind``name`)是必有的,其他都是可选的
136137

137-
(1)`kind`:字符串,表示装饰器类型,可能取以下的值。
138+
(1)`kind`:字符串,表示所装饰对象的类型,可能取以下的值。
138139

139140
- 'class'
140141
- 'method'
141142
- 'getter'
142143
- 'setter'
143-
- 'accessor'
144144
- 'field'
145+
- 'accessor'
145146

146-
这表示一共有六种类型的装饰器。
147+
这表示一共有六种类型的装饰器。本章只介绍前五种装饰器,最后一种`accessor`暂时略过。
147148

148149
(2)`name`:字符串或者 Symbol 值,所装饰对象的名字,比如类名、属性名等。
149150

150-
(3)`addInitializer()`:函数,用来在类的初始化阶段,对方法进行一些处理。以前,这些处理通常放在构造函数里面,早于方法本身执行,现在改为放在装饰器的`context`对象里面,具体例子请参阅《方法装饰器》一节。
151+
(3)`addInitializer()`:函数,用来添加类的初始化逻辑。以前,这些逻辑通常放在构造函数里面,对方法进行初始化,现在改成以函数形式传入`addInitializer()`方法。注意,`addInitializer()`没有返回值。
152+
153+
(4)`private`:布尔值,表示所装饰的对象是否为类的私有成员。
151154

152-
注意,`addInitializer()`函数没有返回值
155+
(5)`static`:布尔值,表示所装饰的对象是否为类的静态成员
153156

154-
4)`private`:布尔值,表示所装饰的方法或属性,是否为私有
157+
6)`access`:一个对象,包含了某个值的 get 和 set 方法
155158

156159
## 类装饰器
157160

@@ -168,49 +171,41 @@ type ClassDecorator = (
168171
) => Function | void;
169172
```
170173

171-
请看下面的例子。
174+
类装饰器接受两个参数:`value`(当前类本身)和`context`(上下文对象)。其中,`context`对象的`kind`属性固定为字符串`class`
175+
176+
类装饰器一般用来对类进行操作,可以不返回任何值,请看下面的例子。
172177

173178
```typescript
174-
class InstanceCollector {
175-
instances = new Set();
176-
install = (value:any, {kind}:any) => {
177-
if (kind === 'class') {
178-
const _this = this;
179-
return function (...args:any[]) {
180-
const inst = new value(...args);
181-
_this.instances.add(inst);
182-
return value;
183-
} as unknown as typeof MyClass;
184-
}
185-
return;
186-
};
179+
function Greeter(value, context) {
180+
if (context.kind === 'class') {
181+
value.prototype.greet = function () {
182+
console.log('你好');
183+
};
184+
}
187185
}
188186

189-
const collector = new InstanceCollector();
190-
191-
@collector.install
192-
class MyClass {}
193-
194-
const inst1 = new MyClass();
195-
const inst2 = new MyClass();
196-
const inst3 = new MyClass();
187+
@Greeter
188+
class User {}
197189

198-
collector.instances // new Set([inst1, inst2, inst3])
190+
let u = new User();
191+
u.greet(); // "你好"
199192
```
200193

201-
上面示例中,类装饰器`@collector.install`将所有实例加入一个集合变量`collector.instances`
194+
上面示例中,类装饰器`@Greeter`在类`User`的原型对象上,添加了一个`greet()`方法,实例就可以直接使用该方法
202195

203-
类装饰器返回的函数,会作为新的构造函数
196+
类装饰器可以返回一个函数,替代当前类的构造方法
204197

205198
```typescript
206199
function countInstances(value:any, context:any) {
207200
let instanceCount = 0;
201+
208202
const wrapper = function (...args:any[]) {
209203
instanceCount++;
210204
const instance = new value(...args);
211205
instance.count = instanceCount;
212206
return instance;
213207
} as unknown as typeof MyClass;
208+
214209
wrapper.prototype = value.prototype; // A
215210
return wrapper;
216211
}
@@ -223,13 +218,16 @@ inst1 instanceof MyClass // true
223218
inst1.count // 1
224219
```
225220

226-
上面示例实现了实例的计数。为了确保`wrapper()`的返回值是`MyClass`的示例,特别加入`A`行,确保两者的原型对象是一致的。否则,新的构造函数`wrapper`的原型对象,与`MyClass`不同,通不过`instanceof`运算符。
221+
上面示例中,类装饰器`@countInstances`返回一个函数,替换了类`MyClass`的构造方法。新的构造方法实现了实例的计数,每新建一个实例,计数器就会加一,并且对实例添加`count`属性,表示当前实例的编号。
222+
223+
注意,上例为了确保新构造方法继承定义在`MyClass`的原型之上的成员,特别加入`A`行,确保两者的原型对象是一致的。否则,新的构造函数`wrapper`的原型对象,与`MyClass`不同,通不过`instanceof`运算符。
227224

228-
类装饰器也可以直接返回一个新的类
225+
类装饰器也可以返回一个新的类,替代原来所装饰的类
229226

230227
```typescript
231228
function countInstances(value:any, context:any) {
232229
let instanceCount = 0;
230+
233231
return class extends value {
234232
constructor(...args:any[]) {
235233
super(...args);
@@ -249,7 +247,7 @@ inst1.count // 1
249247

250248
上面示例中,`@countInstances`返回一个`MyClass`的子类。
251249

252-
下面的例子是通过类装饰器,禁止使用`new`命令调用类
250+
下面的例子是通过类装饰器,禁止使用`new`命令新建类的实例
253251

254252
```typescript
255253
function functionCallable(
@@ -295,38 +293,37 @@ type ClassMethodDecorator = (
295293
) => Function | void;
296294
```
297295

298-
它的上下文对象`context`有以下属性。
296+
根据上面的类型,方法装饰器是一个函数,接受两个参数:`value``context`
297+
298+
参数`value`是方法本身,参数`context`是上下文对象,有以下属性。
299299

300-
- static:布尔值,表示是否为静态方法。
301-
- private:布尔值,表示是否为私有方法。
300+
- `kind`:值固定为字符串`method`,表示当前为方法装饰器。
301+
- `name`:所装饰的方法名,类型为字符串或 Symbol 值。
302+
- `static`:布尔值,表示是否为静态方法。该属性为只读属性。
303+
- `private`:布尔值,表示是否为私有方法。该属性为只读属性。
302304
- access:函数,表示方法的存取器,但是只能用来取值(只有`get()`方法),不能用来赋值(不能定义`set()`方法)。
303305

304-
```typescript
305-
class C {
306-
@trace
307-
toString() {
308-
return 'C';
309-
}
310-
}
306+
方法装饰器会改写类的原始方法,实质等同于下面的操作。
311307

308+
```typescript
312309
function trace(decoratedMethod) {
313-
// 此处略
310+
// ...
314311
}
315-
```
316-
317-
方法装饰器的实质是执行下面的操作。
318312

319-
```typescript
320313
class C {
314+
@trace
321315
toString() {
322316
return 'C';
323317
}
324318
}
325319

326-
C.prototype.toString = trace(C.prototype.toString);
320+
// `@trace` 等同于
321+
// C.prototype.toString = trace(C.prototype.toString);
327322
```
328323

329-
如果装饰器返回一个新的函数,就会替代所装饰的对象。
324+
上面示例中,`@trace`是方法`toString()`的装饰器,它的效果等同于最后一行对`toString()`的改写。
325+
326+
如果方法装饰器返回一个新的函数,就会替代所装饰的原始函数。
330327

331328
```typescript
332329
function replaceMethod() {
@@ -383,12 +380,16 @@ function log(originalMethod:any, context:ClassMethodDecoratorContext) {
383380

384381
const person = new Person('张三');
385382
person.greet()
386-
// "LOG: Entering method 'greet'."
387-
// "Hello, my name is 张三."
388-
// "LOG: Exiting method 'greet'."
383+
// "LOG: Entering method 'greet'."
384+
// "Hello, my name is 张三."
385+
// "LOG: Exiting method 'greet'."
389386
```
390387

391-
下面是装饰器上下文对象的`addInitializer()`方法的例子。类的方法往往会在构造方法里面,进行`this`的绑定。
388+
上面示例中,装饰器`@log`的返回值是一个函数`replacementMethod`,替代了原始方法`greet()`。在`replacementMethod()`内部,通过执行`originalMethod.call()`完成了对原始方法的调用。
389+
390+
方法装饰器的参数`context`对象里面,有一个`addInitializer()`方法。它是一个钩子方法,用来在类的初始化阶段,添加回调函数,这个回调函数就是作为`addInitializer()`的参数传入的。
391+
392+
下面是`addInitializer()`方法的一个例子。我们知道,类的方法往往需要在构造方法里面,进行`this`的绑定。
392393

393394
```typescript
394395
class Person {
@@ -404,9 +405,14 @@ class Person {
404405
console.log(`Hello, my name is ${this.name}.`);
405406
}
406407
}
408+
409+
const g = new Person('张三').greet;
410+
g() // "Hello, my name is 张三."
407411
```
408412

409-
上面例子中,构造方法将`greet()`方法绑定了`this`,这行代码必须放在构造方法里面。现在,它可以移到`addInitializer()`
413+
上面例子中,类`Person`的构造方法内部,将`this``greet()`方法进行了绑定。如果没有这一行,将`greet()`赋值给变量`g`进行调用,就会报错了。
414+
415+
`this`的绑定必须放在构造方法里面,因为这必须在类的初始化阶段完成。现在,它可以移到方法装饰器的`addInitializer()`里面。
410416

411417
```typescript
412418
function bound(
@@ -424,6 +430,8 @@ function bound(
424430

425431
上面示例中,绑定`this`转移到了`addInitializer()`方法里面。
426432

433+
下面再看一个例子,通过`addInitializer()`将选定的方法名,放入一个集合。
434+
427435
```typescript
428436
function collect(
429437
value,
@@ -449,7 +457,7 @@ const inst = new C();
449457
inst.@collect // new Set(['toString', Symbol.iterator])
450458
```
451459

452-
上面示例中,装饰器`@collect`会将所装饰的成员名字,加入一个 Set 集合`collectedMethodKeys`
460+
上面示例中,方法装饰器`@collect`会将所装饰的成员名字,加入一个 Set 集合`collectedMethodKeys`
453461

454462
## 属性装饰器
455463

@@ -469,36 +477,32 @@ type ClassFieldDecorator = (
469477
) => (initialValue: unknown) => unknown | void;
470478
```
471479

472-
注意,装饰器的第一个参数`value`的类型是`undefined`这意味着这个参数实际上是不存在的,即不能从`value`获取目标属性的值
480+
注意,装饰器的第一个参数`value`的类型是`undefined`这意味着这个参数实际上没用的,装饰器不能从`value`获取所装饰属性的值,要用下文的方法获取。另外,第二个参数`context`对象的`kind`属性的值为字符串`field`,而不是“property”或“attribute”,这一点是需要注意的
473481

474-
如果要获取属性的值,必须使用存取器,请看下面的例子
482+
属性装饰器的返回值是一个函数,该函数会自动执行,用来对所装饰属性进行初始化。该函数的参数是所装饰属性的初始值,该函数的返回值是所装饰属性的最终值
475483

476484
```typescript
477-
let acc;
478-
479-
function exposeAccess(
480-
value, {access}
481-
) {
482-
acc = access;
485+
function logged(value, context) {
486+
const { kind, name } = context;
487+
if (kind === 'field') {
488+
return function (initialValue) {
489+
console.log(`initializing ${name} with value ${initialValue}`);
490+
return initialValue;
491+
};
492+
}
483493
}
484494

485495
class Color {
486-
@exposeAccess
487-
name = 'green'
496+
@logged name = 'green';
488497
}
489498

490-
const green = new Color();
491-
green.name // 'green'
492-
493-
acc.get.call(green) // 'green'
494-
495-
acc.set.call(green, 'red');
496-
green.name // 'red'
499+
const color = new Color();
500+
// "initializing name with value green"
497501
```
498502

499-
上面示例中,`@exposeAccess``name`属性的装饰器,它的第二个参数就是`name`的上下文对象,其中`access`属性包含了取值器(`get`)和存值器(`set`),可以对`name`属性进行取值和赋值
503+
上面示例中,属性装饰器`@logged`装饰属性`name``@logged`的返回值是一个函数,该函数用来对属性`name`进行初始化,它的参数`initialValue`就是属性`name`的初始值`green`。新建实例对象`color`时,该函数会自动执行
500504

501-
下面的例子是更改属性的初始值
505+
属性装饰器的返回值函数,可以用来更改属性的初始值
502506

503507
```typescript
504508
function twice() {
@@ -514,6 +518,35 @@ const inst = new C();
514518
inst.field // 6
515519
```
516520

521+
上面示例中,属性装饰器`@twice`返回一个函数,该函数的返回值是属性`field`的初始值乘以2,所以属性`field`的最终值是6。
522+
523+
属性装饰器的上下文对象`context``access`属性,提供所装饰属性的存取器,请看下面的例子。
524+
525+
```typescript
526+
let acc;
527+
528+
function exposeAccess(
529+
value, {access}
530+
) {
531+
acc = access;
532+
}
533+
534+
class Color {
535+
@exposeAccess
536+
name = 'green'
537+
}
538+
539+
const green = new Color();
540+
green.name // 'green'
541+
542+
acc.get(green) // 'green'
543+
544+
acc.set(green, 'red');
545+
green.name // 'red'
546+
```
547+
548+
上面示例中,`access`包含了属性`name`的存取器,可以对该属性进行取值和赋值。
549+
517550
## getter 装饰器,setter 装饰器
518551

519552
getter 装饰器和 setter 装饰器的类型描述如下。

0 commit comments

Comments
 (0)
Please sign in to comment.