2
2
3
3
## 简介
4
4
5
- 装饰器(Decorator)是一种语法结构,用来修改类 (class)的行为。
5
+ 装饰器(Decorator)是一种语法结构,用来在定义时修改类 (class)的行为。
6
6
7
7
在语法上,装饰器有如下几个特征。
8
8
@@ -144,7 +144,7 @@ function decorator(
144
144
- 'field'
145
145
- 'accessor'
146
146
147
- 这表示一共有六种类型的装饰器。本章只介绍前五种装饰器,最后一种` accessor ` 暂时略过。
147
+ 这表示一共有六种类型的装饰器。本章只介绍前五种装饰器,最后一种` accessor ` 暂时略过,它是一个全新的语法提案 。
148
148
149
149
(2)` name ` :字符串或者 Symbol 值,所装饰对象的名字,比如类名、属性名等。
150
150
@@ -275,6 +275,33 @@ robin.name // 'Robin'
275
275
276
276
上面示例中,类装饰器` @functionCallable ` 返回一个新的构造方法,里面判断` new.target ` 是否不为空,如果是的,就表示通过` new ` 命令调用,从而报错。
277
277
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
+
278
305
## 方法装饰器
279
306
280
307
方法装饰器用来装饰类的方法(method)。它的类型描述如下。
@@ -301,7 +328,8 @@ type ClassMethodDecorator = (
301
328
- ` name ` :所装饰的方法名,类型为字符串或 Symbol 值。
302
329
- ` static ` :布尔值,表示是否为静态方法。该属性为只读属性。
303
330
- ` private ` :布尔值,表示是否为私有方法。该属性为只读属性。
304
- - access:函数,表示方法的存取器,但是只能用来取值(只有` get() ` 方法),不能用来赋值(不能定义` set() ` 方法)。
331
+ - ` access ` :对象,包含了方法的存取器,但是只有` get() ` 方法用来取值,没有` set() ` 方法进行赋值。
332
+ - ` addInitializer() ` :为方法增加初始化函数。
305
333
306
334
方法装饰器会改写类的原始方法,实质等同于下面的操作。
307
335
@@ -387,7 +415,35 @@ person.greet()
387
415
388
416
上面示例中,装饰器` @log ` 的返回值是一个函数` replacementMethod ` ,替代了原始方法` greet() ` 。在` replacementMethod() ` 内部,通过执行` originalMethod.call() ` 完成了对原始方法的调用。
389
417
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)的初始化。
391
447
392
448
下面是` addInitializer() ` 方法的一个例子。我们知道,类的方法往往需要在构造方法里面,进行` this ` 的绑定。
393
449
@@ -477,9 +533,9 @@ type ClassFieldDecorator = (
477
533
) => (initialValue : unknown ) => unknown | void ;
478
534
```
479
535
480
- 注意,装饰器的第一个参数` value ` 的类型是` undefined ` ,这意味着这个参数实际上没用的,装饰器不能从` value ` 获取所装饰属性的值,要用下文的方法获取 。另外,第二个参数` context ` 对象的` kind ` 属性的值为字符串` field ` ,而不是“property”或“attribute”,这一点是需要注意的。
536
+ 注意,装饰器的第一个参数` value ` 的类型是` undefined ` ,这意味着这个参数实际上没用的,装饰器不能从` value ` 获取所装饰属性的值。另外,第二个参数` context ` 对象的` kind ` 属性的值为字符串` field ` ,而不是“property”或“attribute”,这一点是需要注意的。
481
537
482
- 属性装饰器的返回值是一个函数, 该函数会自动执行,用来对所装饰属性进行初始化。该函数的参数是所装饰属性的初始值,该函数的返回值是所装饰属性的最终值 。
538
+ 属性装饰器要么不返回值,要么返回一个函数, 该函数会自动执行,用来对所装饰属性进行初始化。该函数的参数是所装饰属性的初始值,该函数的返回值是该属性的最终值 。
483
539
484
540
``` typescript
485
541
function logged(value , context ) {
@@ -549,7 +605,7 @@ green.name // 'red'
549
605
550
606
## getter 装饰器,setter 装饰器
551
607
552
- getter 装饰器和 setter 装饰器的类型描述如下 。
608
+ getter 装饰器和 setter 装饰器,是分别针对类的取值器(getter)和存值器(setter)的装饰器。它们的类型描述如下 。
553
609
554
610
``` typescript
555
611
type ClassGetterDecorator = (
@@ -577,6 +633,12 @@ type ClassSetterDecorator = (
577
633
) => Function | void ;
578
634
```
579
635
636
+ 注意,getter
637
+ 装饰器的上下文对象` context ` 的` access ` 属性,只包含` get() ` 方法;setter
638
+ 装饰器的` access ` 属性,只包含` set() ` 方法。
639
+
640
+ 这两个装饰器要么不返回值,要么返回一个函数,取代原来的取值器或存值器。
641
+
580
642
下面的例子是将取值器的结果,保存为一个属性,加快后面的读取。
581
643
582
644
``` typescript
@@ -609,7 +671,7 @@ function lazy(
609
671
}
610
672
611
673
const inst = new C ();
612
- inst .value
674
+ inst .value
613
675
// 正在计算……
614
676
// '开销大的计算结果'
615
677
inst .value
@@ -618,13 +680,121 @@ inst.value
618
680
619
681
上面示例中,第一次读取` inst.value ` ,会进行计算,然后装饰器` @lazy ` 将结果存入只读属性` value ` ,后面再读取这个属性,就不会进行计算了。
620
682
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
+
621
787
## 装饰器的执行顺序
622
788
623
- 装饰器的执行分为两三个阶段 。
789
+ 装饰器的执行分为两个阶段 。
624
790
625
791
(1)评估(evaluation):计算` @ ` 符号后面的表达式的值,得到的应该是函数。
626
792
627
- (2)应用(application):将调用装饰器后得到的结果,应用于类的定义。其中,类装饰器在所有方法装饰器和属性装饰器之后应用。
793
+ (2)应用(application):将评估装饰器后得到的函数,应用于所装饰对象。
794
+
795
+ 也就是说,装饰器的执行顺序是,先评估所有装饰器表达式的值,再将其应用于当前类。
796
+
797
+ 应用装饰器时,顺序依次为方法装饰器和属性装饰器,然后是类装饰器。
628
798
629
799
请看下面的例子。
630
800
@@ -705,4 +875,5 @@ class Person {
705
875
## 参考链接
706
876
707
877
- [ 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
708
879
0 commit comments