使用decorator将属性分配给非原型

Assigning properties to non-prototype with decorators

本文关键字:原型 分配 属性 decorator 使用      更新时间:2023-09-26

我正在前端/后端数据结构之间构建一个简单的映射。为了做到这一点,我创建了一个装饰器,看起来如下:

function ApiField(
    apiKey: string,
    setFn: (any) => any = (ret) => ret,
    getFn: (any) => any = (ret) => ret
) {
    return function (target: AbstractModel, propertyKey: string) {
        target.apiFieldsBag = target.apiFieldsBag || {};
        _.assign(
            target.apiFieldsBag,
            {
                [propertyKey]: {
                    apiKey: apiKey,
                    setFn: setFn,
                    getFn: getFn
                }
            }
        );
    };
}

这就是我使用它的方式:

class AbstractCar {
    @ApiField('id')
    public id: string = undefined;
}
class BMW extends AbstractCar {
    @ApiField('cylinders')
    public cylinderCount: number;
}
class VW extends AbstractCar {
    @ApiField('yearCompanyFounded')
    public yearEstablished: number;
}

我看到的问题是,实际的对象不是传递给装饰器的,而是它的原型:

__decorate([
    ApiField('yearCompanyFounded')
], VW.prototype, "yearEstablished", void 0);

这意味着,当我在decorator中为实例分配东西时,它总是附加到原型,这反过来意味着我想要定义的属性——只有VW实例——在AbstractCarBMW类上也可用(在本例中,它将是yearEstablished)。这使得在两个不同的类中不可能有两个具有相同名称但不同API字段的属性。

有没有办法规避这种行为?

现在,所有三个类都在向同一对象添加属性。解决此问题的关键是克隆target.data上的对象,以便每个类使用不同的对象,而不是所有类都引用同一个对象。

下面是一个更简单的例子,演示了一种方法:

function ApiField(str: string) {
    return function (target: any, propertyKey: string) {
        // I tested with Object.assign, but it should work with _.assign the same way
        target.data = _.assign({}, target.data, {
            [propertyKey]: str
        });
    };
}
class AbstractCar {
    @ApiField("car")
    public carID;
}
class BMW extends AbstractCar {
    @ApiField("bmw")
    public bmwID;
}
class VW extends AbstractCar {
    @ApiField("vw")
    public vwID;
}
AbstractCar.prototype.data; // Object {carID: "car"}
BMW.prototype.data;         // Object {carID: "car", bmwID: "bmw"}
VW.prototype.data;          // Object {carID: "car", vwID: "vw"}

问题是类中的public不是标准的JavaScript,它只是TypeScript所做的事情。因此,你必须小心,因为你所做的任何事情都可能在未来破裂。

一种可能性是使用Object.assign()添加实例属性(IINM,apiFieldsBag应该从对象文字创建的对象转移到this):

class AbstractCar {
    constructor() {
        Object.assign(this, {
            @ApiField('id')
            id: undefined,
        });
    }
}