2021-12
Refresher: Decorator Capabilities
Users can prefix a decorator with `@init:` to add the ability to add initializers to the value
function bound(fn, { name, addInitializer }) {
addInitializer(function() {
this[name] = this.name.bind(this);
});
}
class MyClass {
message = 'Hello!';
@init:bound callback() {
console.log(this.message);
}
}
let { callback } = new MyClass();
callback(); // Hello!
Design was originally motivated by making decorators more statically analyzable.
@init initializers were interleaved with class field initializers. This meant that implementations would have to check for additional code to run after each decorated element, before the next class field. Can no longer generate static bootstrapping code for class fields based on class shape.
class MyClass {
// This field is assigned _before_ the
// initializers for `method` are run. This
// means that this field uses the
// uninitialized version of `method.`
uninitialized = this.method();
@init:decorate
method() {
// ...
}
// This field is assigned _after_ the
// initializers for `method` are run. This
// means that this field uses the
// fully initialized version of `method.`
initialized = this.method();
}
class MyClass {
// This field is assigned _before_ the
// initializers for `method` are run. This
// means that this field uses the
// uninitialized version of `method.`
uninitialized = this.method();
@init:decorate
method() {
// ...
}
// This field is assigned _after_ the
// initializers for `method` are run. This
// means that this field uses the
// fully initialized version of `method.`
initialized = this.method();
}
class MyClass {
foo = this.method();
@decorate
method() {}
bar = this.method();
}
// Loosely equivalent transpilation:
class MyClass {
static {
this.method = decorate(this.method, { /*...*/ })
}
foo = (runInitializers(), this.method());
method() {}
bar = this.method();
}
Other consequences
class MyClass {
// This was possible before, no longer possible
@init:nonenumerable foo = 123;
// This was never possible. This would run once
// per instance, meaning it would be a really
// inefficient way to change the enumerability.
@init:nonenumerable
method() {
// ...
}
}