admin管理员组文章数量:1122832
I'm trying to make an @enumerable decorator that will expose properties defined via accessor methods.
A function to do this on instances of the class is fairly trivial:
// This works great when called in the class constructor like ```makeEnumerable(this, ['prop1', 'prop2'])```
const makeEnumerable = (what: any, props: string[]) => {
for (const property of props) {
const descriptor = Object.getOwnPropertyDescriptor(what.constructor.prototype, property);
if (descriptor) {
const modifiedDescriptor = Object.assign(descriptor, { enumerable: true });
Object.defineProperty(what, property, modifiedDescriptor);
}
}
};
However, it does not seem possible to turn this into a decorator, because it doesn't have the instance.
// Does not work for Object.keys, Object.getOwnPropertyNames or Object.entries
function enumerable (value: boolean = true): any {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): any {
if (descriptor) {
Object.assign(descriptor, { enumerable: value });
}
};
}
The property does still enumerate in for (const x in y)
structures (strangely), but nowhere else - and worse, Object.entries
throws an error.
Here is an example using the functions above:
class MyClass {
#privateVal1: any;
#privateVal2: any;
constructor () {
makeEnumerable(this, ['b']);
}
@enumerable(true)
get a () {
return this.#privateVal1;
}
set a (val: any) {
this.#privateVal1 = val;
}
get b () {
return this.#privateVal2;
}
set b (val: any) {
this.#privateVal2 = val;
}
}
const enumerableA = new MyClass();
enumerableA.a = 5;
enumerableA.b = 6;
const keys = [];
for (const key in enumerableA) {
keys.push(key);
}
console.log({
'forin': keys, // ['a', 'b']
'keys': Object.keys(enumerableA), // ['b']
'keys(proto)': Object.keys(Object.getPrototypeOf(enumerableA)), // ['a']
'getOwnPropertyNames': Object.getOwnPropertyNames(enumerableA), // ['b']
'getOwnPropertyNames(proto)': Object.getOwnPropertyNames(Object.getPrototypeOf(enumerableA)), // ['constructor', 'a', 'b']
});
console.log({
'entries': Object.entries(enumerableA), // Error('Cannot read private member #privateVal1 from an object whose class did not declare it');
'entries(proto)': Object.entries(Object.getPrototypeOf(enumerableA)), // Error('Cannot read private member #privateVal1 from an object whose class did not declare it');
});
Is there any way to use a decorator to make an accessor method an enumerable property?
I'm trying to make an @enumerable decorator that will expose properties defined via accessor methods.
A function to do this on instances of the class is fairly trivial:
// This works great when called in the class constructor like ```makeEnumerable(this, ['prop1', 'prop2'])```
const makeEnumerable = (what: any, props: string[]) => {
for (const property of props) {
const descriptor = Object.getOwnPropertyDescriptor(what.constructor.prototype, property);
if (descriptor) {
const modifiedDescriptor = Object.assign(descriptor, { enumerable: true });
Object.defineProperty(what, property, modifiedDescriptor);
}
}
};
However, it does not seem possible to turn this into a decorator, because it doesn't have the instance.
// Does not work for Object.keys, Object.getOwnPropertyNames or Object.entries
function enumerable (value: boolean = true): any {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): any {
if (descriptor) {
Object.assign(descriptor, { enumerable: value });
}
};
}
The property does still enumerate in for (const x in y)
structures (strangely), but nowhere else - and worse, Object.entries
throws an error.
Here is an example using the functions above:
class MyClass {
#privateVal1: any;
#privateVal2: any;
constructor () {
makeEnumerable(this, ['b']);
}
@enumerable(true)
get a () {
return this.#privateVal1;
}
set a (val: any) {
this.#privateVal1 = val;
}
get b () {
return this.#privateVal2;
}
set b (val: any) {
this.#privateVal2 = val;
}
}
const enumerableA = new MyClass();
enumerableA.a = 5;
enumerableA.b = 6;
const keys = [];
for (const key in enumerableA) {
keys.push(key);
}
console.log({
'forin': keys, // ['a', 'b']
'keys': Object.keys(enumerableA), // ['b']
'keys(proto)': Object.keys(Object.getPrototypeOf(enumerableA)), // ['a']
'getOwnPropertyNames': Object.getOwnPropertyNames(enumerableA), // ['b']
'getOwnPropertyNames(proto)': Object.getOwnPropertyNames(Object.getPrototypeOf(enumerableA)), // ['constructor', 'a', 'b']
});
console.log({
'entries': Object.entries(enumerableA), // Error('Cannot read private member #privateVal1 from an object whose class did not declare it');
'entries(proto)': Object.entries(Object.getPrototypeOf(enumerableA)), // Error('Cannot read private member #privateVal1 from an object whose class did not declare it');
});
Is there any way to use a decorator to make an accessor method an enumerable property?
Share Improve this question asked Nov 22, 2024 at 12:04 Harry Mustoe-PlayfairHarry Mustoe-Playfair 1,41917 silver badges31 bronze badges 4 |1 Answer
Reset to default 1Prototype vs Instance properties
There's nothing strange in that, you'll have to understand Prototype vs Instance properties
makeEnumerable
sets enumerable descriptors on the instance.enumerable
decorator modifies prototype-level descriptors.
You are expecting Object.keys(enumerableA)
to be ['a', 'b']
, like 'forin': keys
, but:
- The
for...in
loop iterates over both own and inherited enumerable properties. - While,
Object.keys
returns only it's own enumerable properties.
Check this MDN blog for more info. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties#querying_object_properties
Explaination of the Outputs
for (const key in enumerableA)
- Output:
['a', 'b']
- Why:
- The
for...in
loop iterates over both own and inherited enumerable properties. b
anda
are enumerable but on different level.b
is made enumerable bymakeEnumerable
as instance property anda
prototype property made enumerable by the@enumerable
decorator.
- The
Object.keys(enumerableA)
- Output:
['b']
- Why:
Object.keys
lists only the own enumerable properties.b
is made an own enumerable property bymakeEnumerable
function in constructor.a
is still on the prototype, so it is excluded.
Object.keys(Object.getPrototypeOf(enumerableA))
- Output:
['a']
- Why:
- It lists only enumerable properties on the prototype,
@enumerable
decorator modifies the prototype-level descriptor fora
. b
is non-enumerable on prototype becausemakeEnumerable
function made enumerable on instance only.
- It lists only enumerable properties on the prototype,
Object.getOwnPropertyNames(enumerableA)
- Output:
['b']
- Why:
- It lists all own properties (both enumerable and non-enumerable) but ignores prototype properties.
b
is an own property on the instance.
Object.getOwnPropertyNames(Object.getPrototypeOf(enumerableA))
- Output:
['constructor', 'a', 'b']
- Why:
- This lists all own properties (both enumerable and non-enumerable) defined directly on the prototype.
constructor
, andb
are non-enumerable but exist on prototype.
Why Object.entries
throws an error
Object.entries
access all the enumerable own properties.
- When querying
Object.entries(enumerableA)
:- It access
b
as it is enumerable property on instance. While accessingb
, thethis
context ofget b() {...}
is the instanceMyClass { b: [Getter/Setter] }
. - So it works fine, because the private properties in the class are accessible when
this
referes to the instance.
- It access
- But when querying
Object.entries(Object.getPrototypeOf(enumerableA))
- It accesses
a
becausea
is an enumerable property on the prototype. - But it throws an error because the
this
context for theget a(){...}
is the prototype object ({ a: [Getter/Setter] }
), not an instance ofMyClass
- It accesses
You must understand how private properties are handled by typescript.
- Typescript generate some functions to check
this
when a method is called. Ifthis
is not same asown class
it throws an error. - Check compiled Javascript code for more details.
Is there any way to use a decorator to make an accessor method an enumerable property?
No, it is not possible to make instance properties enumerable directly using decorators in Typescript because property decorators in Typescript only have access to the class prototype
for instance members, not the instance itself.
- A property decorator in Typescript is executed before any instances of the class are created. It operates at the class definition level.
- If you want to make
instance properities
enumerable usemakeEnumerable
function, as you used forb
.
I hope, I've addressed all your issues. If anything else you'd like to clarify, feel free to ask. Happy learning!
版权声明:本文标题:javascript - How to create an "enumerable" decorator for accessor methods in TypeScript - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736303863a1932030.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
for (const k in o)
will enumerate more things thanObject.keys(o)
does, because the former will give you all enumerable keys, whereas the latter only gives you own enumerable keys. The whole problem you're having is that getters are really prototype properties, not instance properties. You can certainly make those getters enumerable, but you want them to be own also, which is a different thing altogether. – jcalz Commented Nov 22, 2024 at 17:00