Talking about typescript decorators from java annotations-annotations and decorators

I have compiled the " Basic Principles of Java Annotations (Annotations) " before. In Java, annotations are oil and salt. For JavaScript, it is also an oriental spice in medieval Europe.

Decorators and annotations

Decorators and annotations have not been able to figure out their specific concepts before. I think they are all based on meta-programming. Annotations are a kind of decoration mode.

  • Annotation (Annotation) : only provide additional metadata support , and can not achieve anything . An additional Scanner is required to perform corresponding operations based on metadata.

  • Decorator (the Decorator) : is provided only for the definition of hijacking , the classes can, into the reference class methods, class attribute class and method to be modified . No metadata support is provided.

The connection between annotations and decorators:

Add metadata through annotations, and then get these metadata in the decorator, complete the modification of the class, class methods, etc., you can add metadata support in the decorator, for example, you can use the decorator factory function and the decorator Add metadata support to the function.

The difference between annotations and decorators

Although the syntax is very similar, different concepts may be used in different languages:

  • Languages ​​using Annotation : AtScript, Java, C# (called Attribute).

  • Languages ​​using Decorator : Python, JavaScript/ECMAScript.

Conceptually, we can clearly see that annotations and decorators have no semantics in common!

Annotations and decorators can simulate each other and are not equivalent. The decorator can naturally run at runtime, and the annotation must be reflected (the type itself is not available)

Inheritance mode is an important way to enrich the "connotation" of sub-elements, whether it is inheriting interfaces or subclasses inheriting base classes. The decorator model can pack the existing modules without changing the inheritance relationship to enrich the connotation without affecting the original functions. Compared with inheritance, it is more flexible.

One of the most powerful features of the decorator is its ability to reflect metada (reflect metada)

Why do you need reflection in JavaScript?

Reflection is used to describe code that can inspect other codes in the same system (or itself).

JavaScript applications are getting bigger and bigger, so some tools (such as the inversion of control containers) and functions like (runtime type assertion) are needed to manage this increasing complexity.

A powerful reflection API should allow us to inspect an unknown object at runtime and find out everything about it. We should be able to find something like this:

  1. The name of the entity.

  2. The type of entity.

  3. Which interfaces are implemented by entities.

  4. The name and type of the entity attribute.

  5. The name and type of the entity's constructor parameters

In JavaScript, we can use functions such as Object.getOwnPropertyDescriptor() or Object.keys() to find some information about the entity, but we need to reflect to implement more powerful development tools.

However, things are about to change, because TypeScript starts to support some Reflection features. But in fact they are just some JavaScript functions, which can help us to annotate code or modify the behavior of the code-this practice is usually called metaprogramming.

TypeScript decorator

Decorators can abstract code well, and they are most suitable for packaging logic that may be reused in multiple places.

Five ways to decorate

  • Class declaration

  • Attributes

  • method

  • parameter

  • accessor

Class decorator

The class decorator enables developers to intercept the constructor of the class .

Note: When we declare a class, the decorator will be called instead of waiting until the class is instantiated.

When you decorate a class, the decorator does not take effect on the subclasses of that class. Let us freeze a class to completely prevent other programmers from accidentally forgetting this feature.

@Frozen
class IceCream {}

function Frozen(constructor: Function) {
  Object.freeze(constructor);
  Object.freeze(constructor.prototype);
}

console.log(Object.isFrozen(IceCream)); // true

class FroYo extends IceCream {} // 报错,类不能被扩展

当装饰函数直接修饰类的时候,装饰函数接受唯一的参数constructor,这个参数就是该被修饰类本身。

此外,在修饰类的时候,如果装饰函数有返回值,该返回值会重新定义这个类,也就是说当装饰函数有返回值时,其实是生成了一个新类,该新类通过返回值来定义。

方法装饰器 Method Decorator

方法装饰器来覆写一个方法,改变它的执行流程,以及在它执行前后额外运行一些代码

下面这个例子会在执行真正的代码之前弹出一个确认框。如果用户点击了取消,方法就会被跳过。注意,这里我们装饰了一个方法两次,这两个装饰器会从上到下地执行。

function log(target, key, descriptor) {}
class P {
    @log
    foo() {
      console.log('Do something');
    }
}

对于类的函数的装饰器函数,依次接受的参数为:

  • target:如果修饰的是类的实例函数,那么target就是类的原型。如果修饰的是类的静态函数,那么target就是类本身。

  • key: 该函数的函数名。

  • descriptor:该函数的描述属性,比如 configurable、value、enumerable等。

属性装饰器 Property Decorator

属性装饰器极其有用,因为它以监听对象状态的变化

为了充分了解接下来这个例子,建议你先熟悉一下 JavaScript 的属性描述符(PropertyDescriptor)。

function foo(target,name){}
class P{
   @foo
   name = 'Jony'
}

这里对于类的属性的装饰器函数接受两个参数,

  • 第一个参数:

    • 对于静态属性而言,是类本身

    • 对于实例属性而言,是类的原型,

  • 第二个参数:所指属性的名字

类函数参数的装饰器

类函数的参数装饰器可以修饰类的构建函数中的参数,以及类中其他普通函数中的参数。该装饰器在类的方法被调用的时候执行

function foo(target,key,index){}
class P{
   test(@foo a){
   }
}

类函数参数的装饰器函数接受三个参数

  • target: 类本身

  • key:该参数所在的函数的函数名

  • index: 该参数在函数参数列表中的索引值

装饰器可以起到分离复杂逻辑的功能,且使用上极其简单方便。与继承相比,也更加灵活,可以从装饰类,到装饰类函数的参数,可以说武装到了“牙齿”。

Typescript中的元数据操作

可以通过reflect-metadata包来实现对于元数据的操作。首先我们来看reflect-metadata的使用,首先定义使用元数据的函数:

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

这里的format可以作为装饰器函数的工厂函数,因为format函数返回的是一个装饰器函数,上述的方法定义了元数据Sysmbol("format"),用Sysmbol的原因是为了防止元数据中的字段重复,而format定义了取元数据中相应字段的功能。

接着我们来在类中使用相应的元数据:

class Greeter {
    @format("Hello, %s")
    name: string;

    constructor(name: string) {
        this.name = message;
    }
    sayHello() {
        let formatString = getFormat(this, "name");
        return formatString.replace("%s", this.name);
    }
}

const g = new Greeter("Jony");
console.log(g.sayHello());

在上述中,我们在name属性的装饰器工厂函数,执行@format("Hello, %s"),返回一个装饰器函数,且该装饰器函数修饰了Greeter类的name属性,将“name”属性的值写入为"Hello, %s"。

然后再sayHello方法中,通过getFormat(this,"name")取到formatString为“Hello,%s”.


参考列表:

TypeScript中的装饰器&元数据反射:从新手到专家四 https://zhuanlan.zhihu.com/p/42220487

理解 TypeScript 装饰器 https://zhuanlan.zhihu.com/p/65764702

【认真脸】注解与装饰器的点点滴滴 https://zhuanlan.zhihu.com/p/22277764

聊聊Typescript中的设计模式——装饰器篇(decorators) https://github.com/forthealllight/blog/issues/33




转载本站文章《从java注解漫谈到typescript装饰器——注解与装饰器》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/typescript/2020_0721_8528.html


Guess you like

Origin blog.51cto.com/zhoulujun/2535588