日期:2022年7月23日标签:JavaScript

【Legacy】JavaScript Decorator #

在之前的一篇文章中,讲解过 JavaScript Decorator 的语法,只不过那是新的语法。这篇文章要介绍的是旧的 Decorator 语法。今年我刚入职的公司,项目代码大量用到了 Decorator 语法(Legacy 版本),所以这里打算再介绍一下 JavaScript Decorator:The Legacy Version。

Decorator Pattern #

如果对设计模式比较熟悉的话,应该知道 Decorator Pattern:通过封装一个已有的类获得一个装饰后的类,来达到扩展已有类的功能的模式。

JavaScript Decorator 也是同样的目的,可以用 @Decorator 装饰类、类方法、类属性和类方法参数,通过装饰来达到扩展功能的目的。

目前 Decorator 还没有进入 JavaScript 的标准语法,但是 TypeScript 已经支持了 Decorator,目前 Decorator 正处于 TC39 提案的 stage 2 阶段,所以 原生 JavaScript 最终也会包含 Decorator 的语法。

Decorator 就是一个用来改变类的成员(属性、方法)和类本身的普通的 JavaScript 函数(建议使用纯函数)。当你在类成员和类的头部使用 @decoratorFunction 语法时,decoratorFunction 会被传递一些参数调用,可以用来修改类和类成员。

configuration #

为了能让 tsc 编译 Decorator 语法,需要给 tsc 传递一些参数。

  • target:如果 target 设置为 es5 可能会存在一些问题,所以建议将 target 设置为 ESNext
  • experimentalDecorators:开启 decorator 语法功能
  • emitDecoratorMetaData:decorator metadata 是另外一个实验性的功能,这个选项可以开启支持 decorator metadata 功能

上述几个选项可以在 tsc 命令后添加:

$ tsc --target 'ESNext' --experimentalDecorators --emitDecoratorMetadata

也可以 tsconfig.json 文件中添加:

{
    "compilerOptions": {
        "target": "ESNext",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

想学习 tsc 和 tsconfig.json 的知识?那么可以看看这篇文章:详解 tsconfig.json 文件

官方文档中的一些例子 #

官方文档中摘抄一些例子,进行讲解。

首先,定义一些 decorator,用于装饰 class、method、parameter 和 property。


// 用于装饰 class(类) 的 decorator
export function ClassDecorator(
    constructor: (...args: any[]) => any,
) {
    console.log(`Decorating ${constructor.name}`);
}

// 用于装饰 method(成员方法) 的 decorator
export function MethodDecorator(
    target: any,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor,
) {
    console.log(
        `Decorating method ${propertyKey}` +
        ` from ${target.constructor.name}`,
    );
}

// 用于装饰 parameter(方法的参数) 的 decorator
export function ParameterDecorator(
    target: any,
    propertyKey: string | symbol,
    parameterIndex: number,
) {
    console.log(
        `Decorating parameter ${propertyKey}` +
        ` (index ${parameterIndex})` +
        ` from ${target.constructor.name}`,
    );
}

// 用于装饰 property(成员属性) 的 decorator
export function PropertyDecorator(
    target: any,
    propertyKey: string | symbol,
) {
    console.log(
        `Decorating property ${propertyKey}` +
        ` from ${target.constructor.name}`,
    );
}

定义了 decorator 后,通过在需要装饰的目标头部(前面)添加 @decoratorFun 语法来装饰目标,例如 @ClassDecorator class Foo {}

@ClassDecorator
class Demo {
    @PropertyDecorator
    public foo: string = "foo";

    constructor() {
        console.log("Simple class initialized");
        this.writeGreeting();
    }

    @MethodDecorator
    public get bar() {
        return "bar";
    }

    @MethodDecorator
    public writeGreeting(
        @ParameterDecorator public greeting: string = "Hello, world",
    ) {
        console.log(greeting);
    }
}

const demo = new Demo();

编译执行上面的代码,会输出以下结果。

Decorating property foo from Demo
Decorating method bar from Demo
Decorating parameter writeGreeting (index 0) from Demo
Decorating method writeGreeting from Demo
Decorating Demo
Simple class initialized
Hello, world

执行顺序如下:

  1. 非静态的方法参数、方法和属性的装饰器函数
  2. 静态的方法参数、方法和属性的装饰器函数
  3. 构造函数的参数装饰器
  4. 类装饰器

decorator 工厂 #

decorator 可以是一个高阶函数工厂,返回一个 decorator 函数。这样我们在使用 decorator 时,可以传递一些参数。

export function Decorator(type: string) {
    return (...args: any[]) => {
        console.log(type, args);
    };
}

使用这个 decorator:

@Decorator("class")
class Demo {
    @Decorator("property")
    public foo: string = "foo";

    constructor() {
        console.log("Simple class initialized");
        this.writeGreeting();
    }

    @Decorator("accessor")
    public get bar() {
        return "bar";
    }

    @Decorator("method")
    public writeGreeting(
        @Decorator("parameter") public greeting: string = "Hello, world",
    ) {
        console.log(greeting);
    }
}

const demo = new Demo();

编译执行,输出以下内容:

property [ Demo {}, 'foo', undefined ]
accessor [ Demo {},
  'bar',
  { get: [Function: get bar],
    set: undefined,
    enumerable: false,
    configurable: true } ]
parameter [ Demo {}, 'writeGreeting', 0 ]
method [ Demo {},
  'writeGreeting',
  { value: [Function: writeGreeting],
    writable: true,
    enumerable: false,
    configurable: true } ]
class [ [Function: Demo] ]
Simple class initialized
Hello, world

decorator 组合 #

也可以同时使用多个 decorators 装饰同一个对象。

export function Enumerable(enumerable: boolean = true) {
    console.log(
        `Creating ${enumerable ? "" : "non-"}` +
        `enumerable property factory`,
    );
    return function decorator(
        target: any,
        propertyKey: string | symbol,
        descriptor: PropertyDescriptor,
    ) {
        console.log(
            `Making ${propertyKey}` +
            ` ${enumerable ? "" : "non-"}enumerable`,
        );
        descriptor.enumerable = enumerable;
        return descriptor;
    };
}

class Demo {
    @Enumerable(true)
    @Enumerable(false)
    public resultIsEnumerable() {
        // do nothing
    }
}

编译执行输出以下内容:

Creating enumerable property factory
Creating non-enumerable property factory
Making resultIsEnumerable non-enumerable
Making resultIsEnumerable enumerable

执行的顺序如下:

  1. 第一个 decorator 工厂创建 decorator:Enumerable(true) 执行,返回 decorator
  2. 第二个 decorator 工厂创建 decorator:Enumerable(false) 执行,返回 decorator
  3. 第二个 decorator 工厂创建的 decorator 执行
  4. 第一个 decorator 工厂创建的 decorator 执行

工厂越先执行,工厂返回的 decorator 越后执行。

(完)

目录