New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Static decorators: one more built-in for MVP #260
Comments
Thanks for writing this up. I was sort of alluding to this capability with |
Yep, I've seen it there. Unfortunately, it is about the future which means that for my project that heavily relies on the instance initializers the usage of MVP would require specific hacks and won't be very clear. Regarding use cases: I use a lot of class decorators that creates a system that runs for a particular class. So I need to create a lot of private class properties that contain an internal state of the system. Using the previous proposal I just add new fields to the I definitely can do the same with using a support class extending the user's one, but I would prefer to avoid it because it changes the prototype chain which could be unexpected for a user. Hmm... Maybe it is the only argument I can bring here. However, I still believe that having a tool that doesn't change the prototype chain is excellent. Regarding static initializers: well, I don't think we really need it. Everything can be done via the |
I performed a couple of experiments and want to say that So, the best solution that allows sneaking into the constructor is the following approach: decorator @sneak {
@wrap(target => function () {
const instance = new target();
instance.a = 10;
instance.b = 'foo';
return instance;
})
} It doesn't leave any trace in the prototype chain and gets an ability to execute almost everything after the constructor. But I didn't find a way to invoke anything before the constructor. It means that adding any property during the instantiation makes this property inaccessible in the constructor. E.g., in the BTW, if anyone knows the technique, I would be glad to hear. |
You could use something like this: decorator @injectInitialization(init) {
@wrap(target => {
const proto = target.__proto__;
class Injector {
constructor(...args) {
const instance = Reflect.construct(proto, args, new.target);
init.call(instance);
return instance;
}
};
return function (...args) {
target.__proto__ = Injector;
const instance = new target(...args);
target.__proto__ = proto;
return instance;
};
})
} Demo: function inject(init, target) {
const proto = target.__proto__;
class Injector {
constructor(...args) {
const instance = Reflect.construct(proto, args, new.target);
init.call(instance);
return instance;
}
};
return function (...args) {
target.__proto__ = Injector;
const instance = new target(...args);
target.__proto__ = proto;
return instance;
};
}
class A {
constructor(p) { console.log("A", p) }
}
class B extends A {
constructor(p) { super(p); console.log("B", p, this.x); }
}
var C = inject(function() { this.x = 4 }, B)
new C(3); |
Oh, I just realized that my example only works when decorating derived classes. |
@nicolo-ribaudo It looks entirely like magic here 😄 |
I am currently thinking that we should just generalize initialize to do inject when it's not used on a field. Would that work for you? |
Yeah, where the initializer function is called with an interface like @initialize(([key [, value]) => void) |
Exactly! And if you use initialize on a field multiple times, the outer instance gets called with a single argument. |
Yep, that would be great!
Hmm, I'm not sure I get the idea. Could you explain it a bit? |
It's to avoid re-initializing already initialized fields. The first initializer will install the value on the instance, if subsequent initializers need it they can get it with normal reflection. |
How does the current decorators proposal do for your use cases? |
Note that the current proposal doesn't really give you this kind of |
@littledan, I have taken a look at the new proposal, and I would say that I like this proposal. It is less verbose than the previous one, uses fewer strings and objects in the implementation and looks more natural. However, I would like it to have comparable power with the deprecated stage 2. For now, we are getting back to the opportunities stage 1 proposed plus
@initialize
. Since I'm developing a project which entirely relies on decorators, it would be quite harmful to me.So, here I would propose one more built-in decorator that could simplify the transition to a new proposal for stage 2 projects. I'm not sure about naming, so let's call it
@inject
. It is a reimplementation of{ kind: 'initializer' }
idea. It is a decorator similar to@register
, but it happens duringconstructor
after all the class fields are initialized but before the user-definedconstructor
runs. The implementation is simple:is roughly equivalent to:
As well as
@register
, it could be called multiple times in any cases.I believe it could fill the gap this PR currently has.
The text was updated successfully, but these errors were encountered: