We just need to make this:
class C {
#foo = 1;
get foo () {
return this.#foo;
}
set foo (v) {
this.#foo = v;
}
}
Have the DX of this:
class C {
foo = 1;
}
Ahem, have you heard about the [grouped and auto-accessors proposal](https://github.com/tc39/proposal-grouped-and-auto-accessors)?
Yes, and I’ll get to it soon, please bear with me!
Doing a complex thing requires recreating the simple thing from scratch.
Incremental value requires disproportionate additional effort.
Example: <video controls> and custom video controls
Functionality can be layered on top of the simple thing.
Incremental value requires proportional additional effort.
Example: `Intl.DateTimeFormat`
Use case: Throw if `n` is not a positive number
class C {
n = 0;
}
class C {
#n = 0;
get n () {
return this.#n;
}
set n(v) {
if (!(v >= 0)) {
throw new TypeError("…");
}
this.#n = v;
}
}
Signal Noise
Ahem, the [grouped and auto-accessors proposal](https://github.com/tc39/proposal-grouped-and-auto-accessors) already does this and is Stage 1 and part of decorators!
Yes, on that…
Simple case
class C {
#n = 0;
get n () {
return this.#n;
}
set n(v) {
this.#n = v;
}
}
class C {
accessor n = 1;
}
✅ Excellent signal-to-noise ratio!
Data validation
class C {
#n = 0;
get n () {
return this.#n;
}
set n(v) {
if (!(v >= 0)) {
throw new TypeError("…");
}
this.#n = v;
}
}
class C {
#n = 0;
accessor n {
get () {
return this.#n;
}
set (v) {
if (!(v >= 0)) {
throw new TypeError("…");
}
this.#n = v;
}
}
}
Accessoris still a somewhat obscure term for many JS authors
class C {
accessor foo = 1;
}
Any and all syntax shown is meant to be illustrative
*not all necessarily in scope
0 or more
Exactly one
Useful independently, more powerful together
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) new steps")
}
}
}
}
h/tThanks HE Shi-Jun for the example!
Never accessed outside the accessor
class C {
#foo = 1;
get foo () { return this.#foo; }
set foo (v) { this.#foo = v; }
}
class C {
_foo = 1;
get foo () { return this._foo; }
set foo (v) { this._foo = v; }
}
class C {
[foo] = 1; // symbol
get foo () { return this[foo]; }
set foo (v) { this[foo] = v; }
}
class C {
propertydata foo = 1;
}
class C {
_foo = 1;
get foo () {
return this._foo;
}
set foo (v) {
this._foo = v;
}
}
class C {
_foo = 1;
aliasforwardaliasproxydelegate foo = _foo;
}
class C {
#i = this.attachInternals();
get foo () {
return this.#i.foo;
}
set foo (v) {
this.#i.foo = v;
}
}
class C {
#i = this.attachInternals();
aliasforwardproxydelegate foo = #i.foo;
}
class C implements P{
get foo () {
return this[P.foo];
}
set foo (v) {
this[P.foo] = v;
}
}
class C implements P {
aliasforwardproxydelegate foo = P.foo;
}
ElementInternals, first-class protocols)| Option 1 New MethodDefinition keywords & descriptor keys | Option 2Built-in decorators | |
|---|---|---|
| Example |
|
|
| Scope | More substantial change | Smaller delta |
| Readability | Important bits first | Auxiliary bits first |
| Lossiness | Remain separate from `set` → lossless. Can even be decorated separately! |
Sugar that wraps `set` → lossy |
| Imperative API | `Object.defineProperty()` | None (by design) |
| Object literal support? | Out of the box | [Speculative future extension](https://github.com/tc39/proposal-decorators/blob/master/EXTENSIONS.md) |
| Composeability | Could compose with any member | Can only be applied to accessors |
let obj = {
_n: 0,
get n () {
return this._n;
},
set n (v) {
if (v >= 0) {
this._n = v;
}
}
};
let obj = {
property n: 0,
validate n (v) {
return v >= 0;
}
};
let obj = {
n: 0,
validate n (v) {
return v >= 0;
}
};
let obj = {
get n () {
return this._n;
},
set n (v) {
if (v >= 0) {
this._n = v;
}
}
};
let obj = {
property n,
validate n (v) {
return v >= 0;
}
};
let obj = {
validate n (v) {
return v >= 0;
}
};
class C {
property n = 0;
validate n (v) {
return v >= 0;
}
normalize n (v) {
return Number(v);
}
}
class C {
property n {
validate (v) {
return v >= 0;
}
normalize (v) {
return Number(v);
}
} = 0;
}
| Direction | Option 1 New MethodDefinition keywords & descriptor keys | Option 2Built-in decorators |
|---|---|---|
| Example |
|
|
| Scope | Much substantial change | Smaller delta |
| Readability | Important bits first | Auxiliary bits first. Mitigated through references for longer functions. |
| Lossiness | Remain separate from `set` → lossless. Can even be decorated separately! |
Sugar that wraps set → lossy.
Can be designed to preserve the original setter somewhere.
|
| Imperative API | `Object.defineProperty()` | None (by design). Can be mitigated via object literal decorators. |
I'm looking at my accessors usage, and they are (numbers are not measured): - 75% "property forwarding" - 15% lazy initial computation - 10% validation - 5% other — Nicolò Ribaudo
tc39/Problem statement: There are large classes of accessor use cases with strong commonalities and they deserve better DX and tooling support.
This proposal aims to explore which of these may have good Impact/Effort to expose as built-in decorators.
import isFoo, hasBar from "./validators.js";
const { validate, lazy } = SomeObject;
class C {
// Signature tentative, do not 🚲shed!
@validate(isFoo, hasBar) accessor foo;
@lazy accessor foo {
get () {
return expensiveComputation();
}
}
}
Problem statement: Alias accessors are a particularly large class of accessor use cases and may be worth exploring separately, as they are more likely to need syntax for reasonable DX and to access privates.
class C {
// Indicative syntax, do not bikeshed
alias foo = #foo.value;
alias bar = #internals.bar {
get;
#set;
}
}