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 06c70f3

Browse files
committedJul 27, 2023
docs: finish chapter decorator
1 parent d3313fb commit 06c70f3

File tree

2 files changed

+182
-10
lines changed

2 files changed

+182
-10
lines changed
 

‎chapters.yml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- assert.md: 类型断言
1515
- module.md: 模块
1616
- namespace.md: namespace
17+
- decorator.md: 装饰器
1718
- operator.md: 运算符
1819
- mapping.md: 类型映射
1920
- utility.md: 类型工具

‎docs/decorator.md

+181-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## 简介
44

5-
装饰器(Decorator)是一种语法结构,用来修改类(class)的行为。
5+
装饰器(Decorator)是一种语法结构,用来在定义时修改类(class)的行为。
66

77
在语法上,装饰器有如下几个特征。
88

@@ -144,7 +144,7 @@ function decorator(
144144
- 'field'
145145
- 'accessor'
146146

147-
这表示一共有六种类型的装饰器。本章只介绍前五种装饰器,最后一种`accessor`暂时略过。
147+
这表示一共有六种类型的装饰器。本章只介绍前五种装饰器,最后一种`accessor`暂时略过,它是一个全新的语法提案
148148

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

@@ -275,6 +275,33 @@ robin.name // 'Robin'
275275

276276
上面示例中,类装饰器`@functionCallable`返回一个新的构造方法,里面判断`new.target`是否不为空,如果是的,就表示通过`new`命令调用,从而报错。
277277

278+
类装饰器的上下文对象`context``addInitializer()`方法,用来定义一个类的初始化函数,在类完全定义结束后执行。
279+
280+
```typescript
281+
function customElement(name: string) {
282+
return <Input extends new (...args: any) => any>(
283+
value: Input,
284+
context: ClassDecoratorContext
285+
) => {
286+
context.addInitializer(function () {
287+
customElements.define(name, value);
288+
});
289+
};
290+
}
291+
292+
@customElement("hello-world")
293+
class MyComponent extends HTMLElement {
294+
constructor() {
295+
super();
296+
}
297+
connectedCallback() {
298+
this.innerHTML = `<h1>Hello World</h1>`;
299+
}
300+
}
301+
```
302+
303+
上面示例中,类`MyComponent`定义完成后,会自动执行类装饰器`@customElement()`给出的初始化函数,该函数会将当前类注册为指定名称(本例为`<hello-world>`)的自定义 HTML 元素。
304+
278305
## 方法装饰器
279306

280307
方法装饰器用来装饰类的方法(method)。它的类型描述如下。
@@ -301,7 +328,8 @@ type ClassMethodDecorator = (
301328
- `name`:所装饰的方法名,类型为字符串或 Symbol 值。
302329
- `static`:布尔值,表示是否为静态方法。该属性为只读属性。
303330
- `private`:布尔值,表示是否为私有方法。该属性为只读属性。
304-
- access:函数,表示方法的存取器,但是只能用来取值(只有`get()`方法),不能用来赋值(不能定义`set()`方法)。
331+
- `access`:对象,包含了方法的存取器,但是只有`get()`方法用来取值,没有`set()`方法进行赋值。
332+
- `addInitializer()`:为方法增加初始化函数。
305333

306334
方法装饰器会改写类的原始方法,实质等同于下面的操作。
307335

@@ -387,7 +415,35 @@ person.greet()
387415

388416
上面示例中,装饰器`@log`的返回值是一个函数`replacementMethod`,替代了原始方法`greet()`。在`replacementMethod()`内部,通过执行`originalMethod.call()`完成了对原始方法的调用。
389417

390-
方法装饰器的参数`context`对象里面,有一个`addInitializer()`方法。它是一个钩子方法,用来在类的初始化阶段,添加回调函数,这个回调函数就是作为`addInitializer()`的参数传入的。
418+
利用方法装饰器,可以将类的方法变成延迟执行。
419+
420+
```typescript
421+
function delay(milliseconds: number = 0) {
422+
return function (value, context) {
423+
if (context.kind === "method") {
424+
return function (...args: any[]) {
425+
setTimeout(() => {
426+
value.apply(this, args);
427+
}, milliseconds);
428+
};
429+
}
430+
};
431+
}
432+
433+
class Logger {
434+
@delay(1000)
435+
log(msg: string) {
436+
console.log(`${msg}`);
437+
}
438+
}
439+
440+
let logger = new Logger();
441+
logger.log("Hello World");
442+
```
443+
444+
上面示例中,方法装饰器`@delay(1000)`将方法`log()`的执行推迟了1秒(1000毫秒)。这里真正的方法装饰器,是`delay()`执行后返回的函数,`delay()`的作用是接收参数,用来设置推迟执行的时间。这种通过高阶函数返回装饰器的做法,称为“工厂模式”,即可以像工厂那样生产出一个模子的装饰器。
445+
446+
方法装饰器的参数`context`对象里面,有一个`addInitializer()`方法。它是一个钩子方法,用来在类的初始化阶段,添加回调函数。这个回调函数就是作为`addInitializer()`的参数传入的,它会在构造方法执行期间执行,早于属性(field)的初始化。
391447

392448
下面是`addInitializer()`方法的一个例子。我们知道,类的方法往往需要在构造方法里面,进行`this`的绑定。
393449

@@ -477,9 +533,9 @@ type ClassFieldDecorator = (
477533
) => (initialValue: unknown) => unknown | void;
478534
```
479535

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

482-
属性装饰器的返回值是一个函数,该函数会自动执行,用来对所装饰属性进行初始化。该函数的参数是所装饰属性的初始值,该函数的返回值是所装饰属性的最终值
538+
属性装饰器要么不返回值,要么返回一个函数,该函数会自动执行,用来对所装饰属性进行初始化。该函数的参数是所装饰属性的初始值,该函数的返回值是该属性的最终值
483539

484540
```typescript
485541
function logged(value, context) {
@@ -549,7 +605,7 @@ green.name // 'red'
549605

550606
## getter 装饰器,setter 装饰器
551607

552-
getter 装饰器和 setter 装饰器的类型描述如下
608+
getter 装饰器和 setter 装饰器,是分别针对类的取值器(getter)和存值器(setter)的装饰器。它们的类型描述如下
553609

554610
```typescript
555611
type ClassGetterDecorator = (
@@ -577,6 +633,12 @@ type ClassSetterDecorator = (
577633
) => Function | void;
578634
```
579635

636+
注意,getter
637+
装饰器的上下文对象`context``access`属性,只包含`get()`方法;setter
638+
装饰器的`access`属性,只包含`set()`方法。
639+
640+
这两个装饰器要么不返回值,要么返回一个函数,取代原来的取值器或存值器。
641+
580642
下面的例子是将取值器的结果,保存为一个属性,加快后面的读取。
581643

582644
```typescript
@@ -609,7 +671,7 @@ function lazy(
609671
}
610672

611673
const inst = new C();
612-
inst.value
674+
inst.value
613675
// 正在计算……
614676
// '开销大的计算结果'
615677
inst.value
@@ -618,13 +680,121 @@ inst.value
618680

619681
上面示例中,第一次读取`inst.value`,会进行计算,然后装饰器`@lazy`将结果存入只读属性`value`,后面再读取这个属性,就不会进行计算了。
620682

683+
## accessor 装饰器
684+
685+
装饰器语法引入了一个新的属性修饰符`accessor`
686+
687+
```typescript
688+
class C {
689+
accessor x = 1;
690+
}
691+
```
692+
693+
上面示例中,`accessor`修饰符等同于为属性`x`自动生成取值器和存值器,它们作用于私有属性`x`。也就是说,上面的代码等同于下面的代码。
694+
695+
```typescript
696+
class C {
697+
#x = 1;
698+
699+
get x() {
700+
return this.#x;
701+
}
702+
703+
set x(val) {
704+
this.#x = val;
705+
}
706+
}
707+
```
708+
709+
`accessor`也可以与静态属性和私有属性一起使用。
710+
711+
```typescript
712+
class C {
713+
static accessor x = 1;
714+
accessor #y = 2;
715+
}
716+
```
717+
718+
accessor 装饰器的类型如下。
719+
720+
```typescript
721+
type ClassAutoAccessorDecorator = (
722+
value: {
723+
get: () => unknown;
724+
set(value: unknown) => void;
725+
},
726+
context: {
727+
kind: "accessor";
728+
name: string | symbol;
729+
access: { get(): unknown, set(value: unknown): void };
730+
static: boolean;
731+
private: boolean;
732+
addInitializer(initializer: () => void): void;
733+
}
734+
) => {
735+
get?: () => unknown;
736+
set?: (value: unknown) => void;
737+
init?: (initialValue: unknown) => unknown;
738+
} | void;
739+
```
740+
741+
accessor
742+
装饰器的`value`参数,是一个包含`get()`方法和`set()`方法的对象。该装饰器可以不返回值,或者返回一个新的对象,用来取代原来的`get()`方法和`set()`方法。此外,装饰器返回的对象还可以包括一个`init()`方法,用来改变私有属性的初始值。
743+
744+
下面是一个例子。
745+
746+
```typescript
747+
class C {
748+
@logged accessor x = 1;
749+
}
750+
751+
function logged(value, { kind, name }) {
752+
if (kind === "accessor") {
753+
let { get, set } = value;
754+
755+
return {
756+
get() {
757+
console.log(`getting ${name}`);
758+
759+
return get.call(this);
760+
},
761+
762+
set(val) {
763+
console.log(`setting ${name} to ${val}`);
764+
765+
return set.call(this, val);
766+
},
767+
768+
init(initialValue) {
769+
console.log(`initializing ${name} with value ${initialValue}`);
770+
return initialValue;
771+
}
772+
};
773+
}
774+
}
775+
776+
let c = new C();
777+
778+
c.x;
779+
// getting x
780+
781+
c.x = 123;
782+
// setting x to 123
783+
```
784+
785+
上面示例中,装饰器`@logged`为属性`x`的存值器和取值器,加上了日志输出。
786+
621787
## 装饰器的执行顺序
622788

623-
装饰器的执行分为两三个阶段
789+
装饰器的执行分为两个阶段
624790

625791
(1)评估(evaluation):计算`@`符号后面的表达式的值,得到的应该是函数。
626792

627-
(2)应用(application):将调用装饰器后得到的结果,应用于类的定义。其中,类装饰器在所有方法装饰器和属性装饰器之后应用。
793+
(2)应用(application):将评估装饰器后得到的函数,应用于所装饰对象。
794+
795+
也就是说,装饰器的执行顺序是,先评估所有装饰器表达式的值,再将其应用于当前类。
796+
797+
应用装饰器时,顺序依次为方法装饰器和属性装饰器,然后是类装饰器。
628798

629799
请看下面的例子。
630800

@@ -705,4 +875,5 @@ class Person {
705875
## 参考链接
706876

707877
- [JavaScript metaprogramming with the 2022-03 decorators API](https://2ality.com/2022/10/javascript-decorators.html)
878+
- [TS 5.0 Beta: New Decorators Are Here!](https://plainenglish.io/blog/ts-5-0-beta-new-decorators-are-here), Bytefer
708879

0 commit comments

Comments
 (0)
Please sign in to comment.