为什么在装饰器中使用 Object.defineProperty 创建的 属性 没有显示在对象上?

Why does a property created with Object.defineProperty in a decorator not show up on the object?

我想通过在 class 定义中添加装饰器来验证设置 属性 的值。

function Test() {
    return (target: object, key: string) => {
        let val = '';
        Object.defineProperty(target, key, {
            get: () => val,
            set: (v: string) => {
                if(/.*\s.*/.test(v)) throw new Error(`Id '${v}' must not contain any whitespace!`)
                val = v;
            },
            enumerable: true
        });
    }
}

class FooClazz {
    @Test()
    id: string;

    constructor(id: string) {
        this.id = id;
    }
}

const test = new FooClazz("myId");

console.log(Object.keys(test)) // -> [];

setter 验证程序有效,但是当我尝试注销对象或其键时,id 没有出现。即使我将 enumerable 设置为 true

我做错了什么?

在这种情况下,您将 属性 添加到 FooClazz(而不是添加到 FooClazz 的实例,例如 test),它不会对其执行任何操作实例。

为了确保 testFooClazz 的一个实例,得到 属性,你需要在 FooClazz 的原型上定义它:

function Test() {
    return (target: any, key: string) => {
        
        target.prototype = target.prototype  || {};
        let val = '';
         Object.defineProperty(target.prototype, key, {
            get: () => val,
            set: (v: string) => {
                if(/.*\s.*/.test(v)) throw new Error(`Id '${v}' must not contain any whitespace!`)
                val = v;
            },
            enumerable: true
        });
    }
}

class FooClazz {
    @Test()
    id: string;

    constructor(id: string) {
        this.id = id;
    }
}

const test = new FooClazz("myId");
console.log(Object.keys(test));

Working version on the TS playground.

问题是Test装饰器装饰了FooClazzclass的prototype属性。但是您正在尝试获取 FooClazz 实例的 own 键。虽然 id 属性 存在于它的原型链上:

console.log(Object.keys(test.__proto__)) // -> ["id"];

另一个问题是 Test 装饰器只运行一次(当装饰 FooClazz 的原型时)并且 val 变量在实例中是 'shared' (尽管它可能是故意的?)。

const test = new FooClazz("myId");
const anotherTest = new FooClazz("anotherId");

console.log(test.id);         // -> "anotherId"
console.log(anotherTest.id);  // -> "anotherId"

您可以使用一个技巧来重新定义第一组实例本身的键:

function Test(target: any, key: string) {
    Object.defineProperty(target, key, {
        get: () => '',
        set: function(v: string) {
            var val = '';
            Object.defineProperty(this, key, {
                get: () => val,
                set: (v: string) => {
                    if(/.*\s.*/.test(v)) throw new Error(`Id '${v}' must not contain any whitespace!`)
                    val = v;
                },
                enumerable: true,
            });
            this[key] = v;
        },
    }); 
}

class FooClazz {
    @Test
    id: string;

    constructor(id: string) {
        this.id = id;
    }
}

playground link