【问题标题】:Assigning properties to non-prototype with decorators使用装饰器将属性分配给非原型
【发布时间】:2016-02-28 03:50:18
【问题描述】:

我正在构建前端/后端数据结构之间的简单映射。为此,我创建了一个如下所示的装饰器:

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);

这意味着当我在装饰器中为实例分配东西时,它总是附加到原型上,这反过来意味着我只想定义的属性 VW 实例也可以在 AbstractCar 上使用和BMW 类(在本例中为yearEstablished)。这使得在两个不同的类中不可能有两个同名但 API 字段不同的属性。

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

【问题讨论】:

  • 顺便说一句,您不需要在_.assign( 前面加上target.apiFieldsBag =。您还可以选择使用 ES6 Object.assign() 而不是 _.assign()
  • 是的,当我再次查看问题时,我想到了这一点。无论如何感谢您的提醒:)

标签: javascript typescript decorator ecmascript-next


【解决方案1】:

现在,所有三个类都在向同一个对象添加属性。解决此问题的关键是克隆 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"}

【讨论】:

  • 问题是,我希望 BMW 类从 AbstractCar 继承数据
  • @justNik 啊,好的。我用适合的东西更新了我的答案。
  • 太好了,谢谢!有趣的是,如此复杂的外观如何归结为如此常见的 javascript 问题。谈一谈为树错过福雷斯特
【解决方案2】:

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

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

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

【讨论】:

  • 我实际上使用的是打字稿,所以使用public 应该没问题 :) 但我会试试你的解决方案
  • @justNik 我知道public 是 TypeScript。 public foo = x 的问题在于它不作为实际实体存在(例如属性),它被编译为构造函数中的赋值。因此,不清楚应该修饰什么,因为 ATM 只能修饰类和属性。
  • @justNik 我刚刚查看了修饰的public 编译成的代码,TypeScript 确实修饰了原型的属性。这是一个错误,IMO(它应该在构造函数中装饰 this 的属性)——您可能需要提交错误报告。
  • 啊现在我明白你的意思了。但是是的,我同意错误部分,我将提交:)
  • @AxelRauschmayer 这不是一个错误,因为规范说它应该是原型...... babel 的工作方式相同。此外,访问修饰符不会影响输出。这是语言的编译时唯一方面。
猜你喜欢
  • 2020-06-12
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-06
  • 2020-06-20
  • 1970-01-01
相关资源
最近更新 更多