简体   繁体   English

Typescript / Javascript自定义属性装饰器

[英]Typescript / Javascript custom Property decorators

I've just started to learn in more depth Typescript and ES6 capabilities and I have some misunderstandings regarding how you should create and how it actually works a custom Property Decorator. 我刚刚开始更深入地学习Typescript和ES6功能,并且对如何创建自定义的Property Decorator以及它如何实际工作有一些误解。

This are the sources that I'm following Source1 Source2 这是我关注的资源Source1 Source2

And this is my custom deocrator. 这是我的自定义除草剂。

export const Required = (target: Object, key: string) => {

    let value: any = target[key];

    const getter = () => {
        if (value !== undefined) return value;

        throw new RequiredPropertyError(MetadataModule.GetClassName(target), key, ErrorOptions.RequiredProperty)
    }

    const setter = (val) => value = val;

    if (delete this[key]) {
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true,
        });
    }
}

It is applied like so 像这样应用

export classMyClass{
    @Required
    public Items: number[];
}

What I don't understand is why it works differently then what would you expect. 我不明白的是为什么它的工作原理与您期望的不同。 Well it works but I don't know if it works accordingly to the let's call it "decorator philosophy", 很好,但是我不知道它是否适合我们称之为“装饰者理念”的作品,

Let me explain what I don't understand 让我解释一下我不明白的

Starting with the first line of code. 从第一行代码开始。

 let value: any = target[key]; 

I would expect that the value would be initialized with Items value, but instead is undefined , why? 我希望该value将使用Items值初始化,但undefined ,为什么? how? 怎么样? i really don't understand. 我真的不明白。

I've followed both sources and that first thing that I find it confusing is the fact that one used target[key] to initialize the value while the other used this[key] , shouldn't this refer to the Required actually. 我一直关注这两个资源,发现令我感到困惑的第一件事是,一个人使用target[key]来初始化value而另一个人使用了this[key]this实际上不应该引用Required。

What I also find confusing is this part 我也感到困惑的是这部分

 if (delete this[key]) {
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true,
        });
    }

Firstly, why I need to delete this[key] ? 首先,为什么我需要删除this [key]? from my understanding this should refer to the current object context in my this case Required , and when debugged it is as so. 根据我的理解,在本例中为Required ,它应指代当前对象的上下文,而在调试时则是如此。

Secondly, that Object.defineProperty will create a property with the name of the key on the target class in my case MyClass , but isn't this property already there? 其次,在我的案例MyClass ,该Object.defineProperty将在目标类上创建一个具有键名的属性,但是此属性不存在吗?

Moving forward and setting the value using the setter, how does the setter parameter val know what data it should hold ? 前进并使用设置器设置value ,设置器参数val如何知道应保存哪些数据? I mean where is it coming ? 我的意思是它要去哪里?

Thanks all. 谢谢大家

There's a lot of questions in there, but I'll try to answer them all :) 那里有很多问题,但是我会尽力回答所有问题:)

I would expect that the value would be initialized with Items value, but instead is undefined , why? 我希望该value将使用Items值初始化,但undefined ,为什么?

When your MyClass class is instantiated and your decorator code is run, the value of all properties of the object are undefined (even if you use a TypeScript property initializer, which you aren't). 当实例化MyClass类并运行装饰器代码时,该对象的所有属性的值都undefined (即使您使用的不是TypeScript属性初始化器)。

Shouldn't this refer to the Required ? this不应该指Required吗?

Yes, I think it does in your example. 是的,我认为您的示例中确实如此。 But in your Source1 link , the decorator is defined using a function expression ( function logProperty() ); 但是在Source1链接中 ,装饰器是使用函数表达式( function logProperty() )定义的; in your example, you have switched this to an arrow function expression ( const Required = ( ... ) => ). 在您的示例中,您已将其切换为箭头函数表达式( const Required = ( ... ) => )。 It's likely this switch will change what this refers to. 此开关可能会更改this所指的内容。 To be safe, switch this to target[key] . 为了安全起见,请将其切换为target[key]

Why I need to delete this[key]? 为什么我需要删除此[键]?

This block of code is deleting the original Items property and replacing it with a property of the same name that allows you to spy on the getting and setting of the variable. 此代码块将删除原始的Items属性,并将其替换为具有相同名称的属性,该属性使您可以监视变量的获取和设置。 The delete this[key] line is guarding against the case where the property isn't configurable. delete this[key]行可防止该属性不可配置。 ( The delete operator returns false if the property in question is non-configurable: ). 如果有问题的属性是不可配置的,则delete运算符将返回false 。)

Object.defineProperty will create a property with the name of the key on the target class in my case MyClass , but isn't this property already there? 在我的案例MyClass ,Object.defineProperty将在目标类上创建一个具有键名的属性,但是此属性不存在吗?

Yes, as mentioned above - this code is replacing the property with a new property. 是的,如上所述,此代码将属性替换为新属性。 The new property is set up to allow you observe the getting and setting of this variable. 设置了新属性,使您可以观察此变量的获取和设置。

Moving forward and setting the value using the setter, how does the setter parameter val know what data it should hold ? 前进并使用设置器设置value ,设置器参数val如何知道应保存哪些数据? I mean where is it coming ? 我的意思是它要去哪里?

When your new Items property is set, the setter function is called with the soon-to-be new value of the property as a parameter (this setter function was previously set up as a setter function using Object.defineProperty ). 当你的新Items属性设置,该setter函数被调用的财产即将被新的值作为参数(这个setter功能以前设置为使用setter函数Object.defineProperty )。 The implementation of this setter function sets value , which is a reference to target[key] . setter函数的实现将设置value ,该value是对target[key]的引用。 As a result, the value of Items gets updated. 结果, Items的值被更新。


To better understand how all this is working, try adding in some logging statements and then play around with getting/setting the Items property: 为了更好地理解所有这些是如何工作的,请尝试添加一些日志记录语句,然后尝试获取/设置Items属性:

export const Required = (target: Object, key: string) => {
    let value: any = target[key];

    console.log('Initialize the Required decorator; value:', value);

    const getter = () => {
        console.log('Inside the getter; value is:', value);
        if (value !== undefined) return value;

        throw new RequiredPropertyError(MetadataModule.GetClassName(target), key, ErrorOptions.RequiredProperty);
    };

    const setter = val => {
        console.log('Inside the setter; val is: ', val, 'value is:', value);
        return (value = val);
    };

    if (delete this[key]) {
        console.log('Replacing the "' + key + '" property with a new, configured version');
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
};

export class MyClass {
    @Required 
    public Items: number[];
}

// instantiated your class
var mc = new MyClass();

// set the Items property
mc.Items = [4, 5, 6];

// get the value with no Errors
mc.Items;

// set the Items property to undefined
mc.Items = undefined;

// get the value - an Error is thrown
mc.Items;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM