2

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

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为了propA

现在,如果getset

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

现在的输出是:

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

虽然enumerable被明确设置为falsefor _propAin f

因此,尽管这些行为可能很奇怪,但我想了解这里发生了什么,以及我将如何实现我想要得到的东西?

4

2 回答 2

3

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

这是诀窍:由于在装饰器中覆盖属性在装饰时起作用(只有可枚举的行为似乎被破坏),您可以定义属性但使用初始化函数代替getterand 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
        });
    };
}

哪个输出:

[ 'propA', 'propB' ] 1 1 A { propA: [Getter/Setter], propB: null }

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

它还enumerable正确支持:设置enumerabletruefor 属性pKey,输出将是:

[ '_propA', 'propA', 'propB' ] 1 1 A { _propA: 1, propA: [Getter/Setter], propB: null }

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

于 2017-05-13T12:37:42.450 回答
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"]
于 2018-08-09T19:18:56.167 回答