Typescript装饰器和Object.defineProperty奇怪的行为

时间:2017-05-13 08:24:22

标签: typescript properties decorator defineproperty

我试图实现一个覆盖属性(1)并定义隐藏属性(2)的装饰器。假设以下示例:

 comboBox1.DataSource = ds.Tables[0].Copy();

哪个输出:

function f() {
    return (target: any, key: string) => {

        let pKey = '_' + key;

        // 1. Define hidden property
        Object.defineProperty(target, pKey, {
            value: 0,
            enumerable: false,
            configurable: true,
            writable: true
        });

        // 2. Override property get/set
        return Object.defineProperty(target, key, {
            enumerable: true,
            configurable: true,
            get: () => target[pKey],
            set: (val) => {
                target[pKey] = target[pKey] + 1;
            }
        });
    };
}

class A {
    @f()
    propA = null;
    propB = null;
}

let a = new A();

console.log(Object.keys(a), a.propA, a._propA, a);

但是,我宁愿期待:

[ 'propB' ] 1 1 A { propB: null }

[ 'propA', 'propB' ] 1 1 A { propA: 1, propB: null } enumerabletrue

现在,如果我用

替换propAget
set

输出现在是:

get: function () {
    return this[pKey]
},
set: function (val) {
    this[pKey] = this[pKey] + 1;
}

虽然[ '_propA', 'propB' ] 1 1 A { _propA: 1, propB: null } enumerable的{​​{1}}明确设置为false

所以,尽管这些行为可能很奇怪,但我想了解这里发生了什么,以及我将如何实现我想要实现的目标?

2 个答案:

答案 0 :(得分:2)

好吧,所以我花了一段时间,但我找到了一个解决方法。问题似乎是Object.defineProperty在装修时不能正常工作。如果你在运行时这样做,事情会按预期进行。那么,如何在装饰器中定义属性,但在运行时?

这是诀窍:因为覆盖装饰器内的属性在装饰时工作(只有可枚举的行为似乎被破坏),你可以定义属性但使用初始化函数代替getter和{ {1}}。该函数将在第一次分配属性(setter)或访问(set)时运行。当发生这种情况时,单词get引用对象的运行时实例,这意味着您可以在装饰时正确初始化您打算做的事情。

以下是解决方案:

this

哪个输出:

function f() {
    return (target: any, key: string) => {
        let pKey = `_${key}`;

        let init = function (isGet: boolean) {
            return function (newVal?) {
                /*
                 * This is called at runtime, so "this" is the instance.
                 */

                // Define hidden property
                Object.defineProperty(this, pKey, {value: 0, enumerable: false, configurable: true, writable: true});
                // Define public property
                Object.defineProperty(this, key, {
                    get: () => {
                        return this[pKey];
                    },
                    set: (val) => {
                        this[pKey] = this[pKey] + 1;
                    },
                    enumerable: true,
                    configurable: true
                });

                // Perform original action
                if (isGet) {
                    return this[key]; // get
                } else {
                    this[key] = newVal; // set
                }
            };
        };

        // Override property to let init occur on first get/set
        return Object.defineProperty(target, key, {
            get: init(true),
            set: init(false),
            enumerable: true,
            configurable: true
        });
    };
}

此解决方案支持默认值,因为它们是在初始化正确的get / set之后分配的。

它还正确支持[ 'propA', 'propB' ] 1 1 A { propA: [Getter/Setter], propB: null } :将enumerable设置为enumerable以获取属性true,输出将为:

pKey

它并不漂亮,我知道,但据我所知,它可以工作并且不会降低性能。

答案 1 :(得分:0)

我检查了您的代码,发现它将定义该属性两次。我修改了您的代码。

 class A {
    @dec
    public value: number = 5
 }

 function dec(target, key) {
    function f(isGet: boolean) {
      return function (newValue?: number) {
          if (!Object.getOwnPropertyDescriptor(this, key)) {
             let value: number;
             const getter = function () {
               return value
             }

             const setter = function (val) {
                value = 2 * val
             }
              Object.defineProperty(this, key, {
                     get: getter,
                     set: setter,
                     enumerable: true,
                     configurable: true
                     })
          }  
          if (isGet) {
              return this[key]
          } else {
              this[key] = newValue
          }
      }
  }

  Object.defineProperty(target, key, {
      get: f(true),
      set: f(false),
      enumerable: false,
      configurable: false
  })
}

const a = new A()
console.log(Object.keys(a))

我们将进入控制台

["value"]